Serverspecを複数台に実行した結果から手っ取り早くサマリを作成する

# TL;DR

- Serverspecを複数台に実行すると結果が見辛いよ
- サーバごとに``23 examples, 0 failures``←これだけまとめたCSVが欲しいよ
- jsonを利用すれば簡単に作れるよ
- afterフックを使うと変換を自動化できるよ

こんな感じ↓のファイルが出力されます。

```sh:audit/last_exec_summary.csv
date,"2018-08-09T22:34:42+09:00"

target,status,summary
"192.168.1.100","failed","32 examples, 23 failures"
"192.168.1.120","failed","32 examples, 1 failure"
"192.168.1.121","passed","23 examples, 0 failures"
```

# 概要

[※仕組みはだいたい前回と同じ](https://qiita.com/S_SenSq/items/d90f6ec01c9cc0da92d3)なので、重複する部分は省略します

前回のはサーバごとにCSVを作成したが、
今回は実行対象全サーバのサマリだけをまとめた一つのCSVファイルを作成する。

Serverspecを複数台に実行する方法はググればたくさん出てくるが、
ログをどのように保存するかはあまり見かけない。

単純にサーバごとの結果をサーバごとのファイルに書き出すのは、``Rakefile``で``rspec_opts``に``format``オプションを指定することで簡単に行える。
具体的には[前回の記事](https://qiita.com/S_SenSq/items/d90f6ec01c9cc0da92d3#json-to-csv)を参照

ただし、基本的にServerspecのログとして出てくる情報は「詳細な結果」だけのため、
結果を確認する際に全サーバのログを一つ一つ確認する必要があり、意外と手間がかかってしまう。
しかし、実際に詳細な結果を見る必要があるのは「テストが失敗したサーバ」だけであり、
すべて合格したサーバは「すべて合格した」ということだけがわかれば十分なはずである。

要するに、Serverspec実行後にサーバごとに表示される
``23 examples, 0 failures``
この表記だけをまとめた簡易的な結果があれば確認の手間がだいぶ減ることになる。

シェル芸を頑張ったりcustom formatterを作ったりしても実現は可能であるが、
今回もJSONを利用する方法で実現する。


# 簡易結果保存先のファイルを初期化

全サーバの結果を一つのファイルに出力するため、Serverspec実行時に出力先ファイルを初期化（あるいは作成）しておく必要がある。
これを行っておかないと、Serverspecを実行するたびにファイルに追記されていってしまう。
（過去のログを取っておくために敢えて追記させてもよいが、その場合はファイル名を実行時刻などのユニークなものにするなどして分けた方がいいと思う）

これはServerspec実行時に一度だけ呼ばれるファイルで行えばよいので、Rakefileに以下のように書けばよい。
なお、出力先パスの情報はspec_helperでも必要になるため、パスは環境変数に入れておくことにする。
実行時刻もわかった方が何かと都合が良いので、ここでファイルに書き出しておく。

```rb:Rakefile
last_exec_summary = "audit/last_exec_summary.csv"
ENV['LAST_EXEC_SUMMARY_FILE'] = last_exec_summary
File.open(last_exec_summary, "w") do |file|
  require 'date'
  daytime = DateTime.now
  file.puts "date,\"#{daytime}\""
  file.puts ""
  file.puts "target,status,summary"
end
```


# サーバごとの簡易結果を出力

[前回の記事](https://qiita.com/S_SenSq/items/d90f6ec01c9cc0da92d3#json-to-csv)でも記載した通り、JSONは以下のような形式で出力される。

```json
{
  "version": "3.8.0",
  "examples": [
    {
      "id": "./spec/hoge/main_spec.rb[1:1:1:1]",
      "description": "should be enabled",
      "full_description": "sshdサービス が有効であること Service \"sshd\" should be enabled",
      "status": "passed",
      "file_path": "./spec/hoge/main_spec.rb",
      "line_number": 6,
      "run_time": 1.606149747,
      "pending_message": null
    },
    {
      "id": "./spec/hoge/main_spec.rb[1:2:1:1]",
      "description": "should be running",
      "full_description": "sshdサービス が起動していること Service \"sshd\" should be running",
      "status": "passed",
      "file_path": "./spec/hoge/main_spec.rb",
      "line_number": 11,
      "run_time": 0.068922039,
      "pending_message": null
    }
  ],
  "summary": {
    "duration": 1.688882982,
    "example_count": 2,
    "failure_count": 0,
    "pending_count": 0,
    "errors_outside_of_examples_count": 0
  },
  "summary_line": "2 examples, 0 failures"
}
```

今回は``summary``と``summary_line``を使う。
方法も前回と同じく、事前にJSONを加工して出力するシェルスクリプトを作成しておき、
spec_helperのafterフックでServerspec実行終了後にそのシェルスクリプトを実行させる。

```bash
.
├── audit
│   ├── last_exec_summary.csv
│   └── json
│       ├── target1.json
│       └── target2.json
├── out_summary.sh
├── Rakefile
└── spec
　   └── spec_helper.rb
```

```rb:spec/spec_helper.rb
## 省略 ##
last_exec_summary = ENV['LAST_EXEC_SUMMARY_FILE']  # Rakefileで指定したパスを取得
RSpec.configure do |c|
  target  = ENV['TARGET_HOST']
  ## 省略 ##
  c.after(:suite) do
    # RSpecのタスクが実行終了するまでjsonファイルへの書き込みが行われないようなので、バックグラウンドで遅延実行させる
    system("sh ./out_summary.sh #{target} #{last_exec_summary} &")
  end
end
```

```sh:out_summary.sh
#!/bin/sh

TARGET=$1
LAST_EXEC_SUMMARY=$2

# jsonファイルに書き込みが終了するのを待つ
sleep 2

# 実行ログにsummaryを出力
FAILURE_COUNT="$(cat audit/json/$TARGET.json | jq '.summary' | jq '.failure_count')"
if [ $FAILURE_COUNT -ne 0 ]
then
  STATUS="failed"
else
  STATUS="passed"
fi

echo "\"$TARGET\",\"$STATUS\","$(cat audit/json/$TARGET.json | jq '.summary_line') >> $LAST_EXEC_SUMMARY
```

本当にsummary_lineだけを出力するだけでもよいが、全合格かそうでないかが一目でわかると便利なので、if文の部分で判定を行っている。
これを実行すると以下のようなファイルが出力される。

```sh:audit/last_exec_summary.csv
date,"2018-08-09T22:34:42+09:00"

target,status,summary
"192.168.1.100","failed","32 examples, 23 failures"
"192.168.1.120","failed","32 examples, 1 failure"
"192.168.1.121","passed","23 examples, 0 failures"
```

上記を見ると、ざっくりと全体の結果がわかって次の行動を考えやすくなる。

- ``192.168.1.100``は、失敗が多すぎる（→設定を入れ忘れている？テスト対象を間違えている？→色々確認が必要）
- ``192.168.1.120``は、一つだけ失敗している（→詳細結果を見てみよう）
- ``192.168.1.121``は、すべて合格している（→詳細結果を見る必要は無い）