Scalaの勉強兼ねて、ChatOpsの実践導入をしたみた

最近Scalaをやることになって
勉強のためにと前から興味のあったChatOpsに挑戦してみたら、これが割りと良い題材だった。

Botにさせる処理によっては、
定型文をBotにしゃべらせるだけの簡単なものから
APIたたいて返ってきたjson解析したり、
sshのライブラリを使ってサーバに入って何か処理したりと
基礎的なことをいろいろできるので、新しい言語を勉強したいという方には良いかもしれません。

# ソース
[slack-scala-client](https://github.com/gilbertw1/slack-scala-client)というライブラリがあったので、今回はこれを使用。

```Main.scala
import slack.models._

object BotRunner extends App {
  SlackBot.client.onEvent {
    // 人からのメッセージがきたら処理
    case e: Message => SlackBot.hears(e)
    case _          =>
  }
}
```

フックになる、イベントが何種類か用意されている。
- 人からのメッセージがきたら
- Botからのメッセージがきたら
- チャンネルに人が追加されたら
などなど。詳しくは[ココ](https://github.com/gilbertw1/slack-scala-client/blob/master/src/main/scala/slack/models/Events.scala)

現状は人が特定のコマンドを打ったら反応する機能しかないので、case でわけているものの処理は1つ。

```SlackBot.scala
import akka.actor.ActorSystem
import slack.rtm.SlackRtmClient
import slack.models.Message

object SlackBot {
  implicit val system = ActorSystem("slack")
  implicit val ec     = system.dispatcher

  private[this] val token = sys.env("SLACK_BOT_TOKEN")  // 予め環境変数にSlackBotのTokenを設定しておく
  val client              = SlackRtmClient(token)
  val selfId              = client.state.self.id

  // メッセージを受けた時に行う処理。優先順位(高い -> 低い(IdleTalk))
  val processes = Seq(Deploy,
                      SparkAppList,
                      SparkAppSelect,
                      ZeppelinRestartor,
                      IdleTalk)

  // 人からのメッセージを受けた時に行う処理
  def hears(message: Message) = {
    // キーワードに引っかかるものだけ実行(最初にヒットしたもののみ)
    processes.filter(_.check(message)).headOption.map(_.run(message))
  }
}
```

Botオブジェクトでは、コマンドの登録と
メッセージ内にキーワードが含まれているかチェックし、含まれていたらその処理を行うようになっている。
SLACK_BOT_TOKENは予め取得して、環境変数で設定しておいてください。

```BotProcess.scala
import slack.models.Message

// 基底クラス
abstract class BotProcess extends PermissionConfig {
  protected[this] val matchPattern: String  // 処理に入るためのコマンド(正規表現も可)

  // メッセージが
  def check(message: Message): Boolean = {
    message.text matches matchPattern
  }

  // checkが通った時に行う処理
  def run(message: Message)
}

// 派生クラス
object TestProcess extends BotProcess {
  override val matchPattern = s"""^<@${SlackBot.selfId}>: test"""
  override def run(message: Message) = {
    // Botにしゃべらせる
    SlackBot.client.sendMessage(message.channel, "テストだよ")
  }
}
```

処理クラスの内容としては
- check: メッセージ内にキーワードが含まれているかチェック
- run: checkの結果がtrueだった時に行う処理

新しいコマンドを追加するときは、BotProcessを継承して、
どんなメッセージがきたら反応するのかを示す matchPattern と
どんな処理をさせるかの run をoverrideして変更する。

matchPatternの最初に```<@${SlackBot.selfId}>```がついているのは
Botに対して、Mentionしてきた時にだけ反応させたいため。
こうしておかないと、誰かが何かメッセージを書く度に反応してしまう。

実際に動かしているものは、チャンネル毎に打てるコマンドの管理だったり
ヘルパー関連の処理などがあるものの、個人で作る分にはあまり必要ないので割愛します。

# 実装済みコマンド
## デプロイ
CircleCIから最新のbuild済みjarをSparkクラスタの特定のパスに配置して、アプリケーションの再起動を行う。
sshライブラリを使って、Sparkのサーバへ入ってexec関数でコマンドを実行していたが
sudoコマンドを使いたいときは、exec ではなく execPTY にしないといけないことに気づかず結構悩んだ。

## jarの管理(バージョンのリスト、バージョン切り替え)
デプロイで再起動した結果、万が一正常に動かなかった時用に前のバージョンにも戻れるように作った。

## Zeppelinの再起動
一時期、Zeppelinサーバの調子が定期的に悪くなるという現象が起こっていたため
サーバに入らずとも再起動ができるようにした。

## 人工知能ボットAPIを使った雑談
[User Local](http://ai.userlocal.jp/)さんの人工知能ボットAPIを前に応募していて使えるようになったので組み込んでみた。
雑談ができたところで特に仕事上メリットはないけど、楽しい。

## Gitのプルリク作成、イシューの作成
GitAPIも試してみたかったので入れてみた。

## ヘルプ
Botに対して、どんなコマンドを打つとどんな処理をしてくれるのか、教えてくれるもの。
コマンドが増えてくるとこれがないと覚えてられないので重要。

# まとめ
新しい言語を勉強するとき、ただただ参考書を読んでもなかなか身につかないし
かと言って何を作ればいいのか…という人はChatOpsおすすめです。

それにしてもScala難しい…今までがずっとPythonをやっていたからというのもあるのだろうけど。
Right型やLeft型という直感ではわからない型があったり
JArrayという型のものに対して、Arrayの時と同じようにforeachとか使うとそんな関数ないよってエラーになったり
JStringはいいけどStringはダメみたいな関数があったりと
やたら型が多い割に、型に厳しいから覚えることが多くて大変。
Scalaマスターへの道のりは長そう