elasticsearchを使いやすくするejというコマンドラインツールを作った

[toyama0919/ej](https://github.com/toyama0919/ej)というツールを作りました。

ちょっとデータ確認したいだけなのにkibana開いて検索条件を入れるのは結構な手間です。

かといってcurlも長くて辛いし、、json書くのがどうも慣れません。

ejというruby製のCLIを作ったので紹介します。

## インストール
```bash
$ gem install ej
```

## demo
![ej4.gif](https://qiita-image-store.s3.amazonaws.com/0/26670/116a381c-98f6-aa72-fbd9-ddc4b179b744.gif)



## 簡単な使い方

### bulkでテストデータ投入して検索する
```bash
$ echo '[{"hoge":"huga"},{"fuga":"hoge"}]' | ej -b -t test

$ ej -s
[
  {
    "hoge": "huga",
    "@timestamp": "2014-12-10T00:12:21+09:00"
  },
  {
    "fuga": "hoge",
    "@timestamp": "2014-12-10T00:12:21+09:00"
  }
]

$ ej -s --no-source-only
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1.0,
    "hits": [
      {
        "_index": "logstash-2014.12.10",
        "_type": "test",
        "_id": "AUovm7ex-veSC_eoFXXQ",
        "_score": 1.0,
        "_source": {
          "hoge": "huga",
          "@timestamp": "2014-12-10T00:12:21+09:00"
        }
      },
      {
        "_index": "logstash-2014.12.10",
        "_type": "test",
        "_id": "AUovm7ex-veSC_eoFXXR",
        "_score": 1.0,
        "_source": {
          "fuga": "hoge",
          "@timestamp": "2014-12-10T00:12:21+09:00"
        }
      }
    ]
  }
}
```
* indexを指定しない場合、index名は「logstash-YYYY.MM.DD」になる。
  * インデックスを指定したい場合は-iで指定できる。
  * -t(_type)は必須。



### qiitaの新着記事を投入
```bash
$ curl -s https://qiita.com/api/v1/items | ej -b -t posts --timestamp_key updated_at --id_keys id
```
* timestamp_keyを指定すると@timestampがそのfieldのtimeになる
* id_keysがidになる。複数指定された場合_区切りで結合される。


### 超簡単な検索
```bash
$ ej -s
[
  {
    "id": 185872,
    "uuid": "c3ed2906afd2f2987e82",
    "user": {
      "id": 57077,
      "url_name": "awm-kaeruko",
      "profile_image_url": "https://avatars.githubusercontent.com/u/7751075?v=2"
    },
    "title": "未経験者でもできるMMDキャラモデ講座を未経験者が読む(駄記事)",
    "created_at": "2014-11-23 20:54:42 +0900",
    "updated_at": "2014-11-23 21:06:56 +0900",
    "created_at_in_words": "17分",
    "updated_at_in_words": "5分",
    "tags": [
      {
        "name": "Blender",
        "url_name": "blender",
        "icon_url": "https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/9c446eb5e04d19aae0cbad19d81c2e88750135c4/medium.jpg?1398258621",
        "versions": [
          "2.7.2"
        ]
      },
....
```
* searchの際のdefaultのindexは_all。(省略可能)
* searchの際のdefaultのhostはlocalhost。(省略可能)

### lucene queryで検索
```bash
$ ej -s -q "ip_address: 127.0.0.1" -h elasticsearch-host -i logstash-2014.01.01
```
* lucene queryに関しては[ここ](http://qiita.com/toyama0919/items/9681ae866ce23a637316)参照


### 超簡単な検索2(hostを指定)
```bash
$ ej -s -h elasticsearch-host
```

### 検索例3(portまで指定)
```bash
$ ej -s -h elasticsearch-host:9201
```

### 検索例4(indexを指定)
```bash
$ ej -s -h elasticsearch-host -i logstash-2014.01.01
```

### 検索例5(特定のfieldだけ取得)
```bash
$ ej -s -q "comment_count: 0" --fields comment_count title
[
  {
    "comment_count": 0,
    "title": "未経験者でもできるMMDキャラモデ講座を未経験者が読む(駄記事)"
  },
  {
    "comment_count": 0,
    "title": "spray-jsonでキー名をスネークケースにしたい"
  },
  {
    "comment_count": 0,
    "title": "Android初心者がAndroidStudioを５日間を使ってみた感想。"
  },
  {
    "comment_count": 0,
    "title": "phpDocumentorをインストールしてドキュメント生成"
  },
  {
    "comment_count": 0,
    "title": "Railsでキーワードをハイライトする"
  },
  {
    "comment_count": 0,
    "title": "CentOSでDockerの`--insecure-registry`を指定する"
  },
  {
    "comment_count": 0,
    "title": "プチコン3号（SMILE BASIC）基礎文法最速マスター"
  },
  {
    "comment_count": 0,
    "title": "Google Maps API で tweet heatmap を作る"
  },
  {
    "comment_count": 0,
    "title": "R から MongoDB に格納されたデータを扱う"
  },
  {
    "comment_count": 0,
    "title": "Mac に RStudio を導入する"
  }
]
```

### index一覧
```bash
$ ej -l -h host01
[
  {
    "health": "green",
    "status": "open",
    "index": "qiita",
    "pri": "1",
    "rep": "0",
    "docs.count": "20",
    "docs.deleted": "0",
    "store.size": "330.9kb",
    "pri.store.size": "330.9kb"
  }
]
```

### mappingを出力
```bash
$ ej -m -h host01
{
  "qiita": {
    "mappings": {
      "posts": {
        "dynamic_templates": [
          {
            "string_template": {
              "mapping": {
                "index": "not_analyzed",
                "type": "string"
              },
              "match": "*",
              "match_mapping_type": "string"
            }
          }
        ],
        "_all": {
          "enabled": false
        },
        "_source": {
          "compress": true
        },
        "properties": {
          "@timestamp": {
            "type": "date",
            "format": "dateOptionalTime"
          },
          "body": {
            "type": "string",
            "index": "not_analyzed"
          },
          "comment_count": {
            "type": "long"
          },
          "created_at": {
            "type": "string",
            "index": "not_analyzed"
          },
          "created_at_as_seconds": {
            "type": "long"
          },
...

```

### facet(ストック数別件数)
```bash
$ ej -f stock_count
{
  "took": 45,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "hits": {
    "total": 20,
    "max_score": 0.0,
    "hits": [

    ]
  },
  "facets": {
    "terms": {
      "_type": "terms",
      "missing": 0,
      "total": 20,
      "other": 0,
      "terms": [
        {
          "term": 0,
          "count": 15
        },
        {
          "term": 1,
          "count": 4
        },
        {
          "term": 2,
          "count": 1
        }
      ]
    }
  }
}
```

### aggregationによるcount(ストック数別件数)
```bash
$ ej aggs stock_count
{
  "took": 209,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "hits": {
    "total": 97,
    "max_score": 0.0,
    "hits": [

    ]
  },
  "aggregations": {
    "agg_stock_count": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": 0,
          "doc_count": 82
        },
        {
          "key": 1,
          "doc_count": 10
        },
        {
          "key": 2,
          "doc_count": 4
        },
        {
          "key": 3,
          "doc_count": 1
        }
      ]
    }
  }
}
```

### queryを指定したfacet
#### ストックがあるpostのストック数別件数
```bash
$ ej -f stock_count -q "stock_count: (>0)"
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "hits": {
    "total": 20,
    "max_score": 0.0,
    "hits": [

    ]
  },
  "facets": {
    "terms": {
      "_type": "terms",
      "missing": 0,
      "total": 5,
      "other": 0,
      "terms": [
        {
          "term": 1,
          "count": 4
        },
        {
          "term": 2,
          "count": 1
        }
      ]
    }
  }
}
```


## 別hostにcopy

### indexごとコピー

```bash
$ ej copy --source localhost --dest 192.168.33.100 -i logstash-2014.11.23
I, [2014-11-23T21:31:57.037700 #15817]  INFO -- : copy complete 20/20
```
* 上記の例だと１１月２４日になってから実行するみたいな使い方がいいかも。。
* idも含めてコピーする(ある意味冪等な結果になる)

### 条件指定してコピー

```bash
$ ej copy --source localhost --dest 192.168.33.100 -i qiita -q "stock_count:(>0)"
I, [2014-11-23T21:31:57.037700 #15817]  INFO -- : copy complete 5/5
```
* -qで部分的なコピーも出来る

## index削除

### indexごと削除

```bash
$ ej delete -i logstash-2014.08.27
```

### typeまで指定して削除

```bash
$ ej delete -i logstash-2014.08.27 -t posts
```

### 条件指定して削除

```bash
$ ej delete -q 'stock_count: 0' -i logstash-2014.08.27
```

## 備考
* ruby1.9.3以上なら動作します。
* 複雑なことは想定していません。
* 業務で使っているうちに機能がてんこ盛りになってしまった。。
* もし使ってくれたらFBください([ここ](https://github.com/toyama0919/ej)まで)
* Clojure製で[似たようなの](https://github.com/elasticsearch/es2unix)があった。。よく出来てそう