KibanaのSaved Objectsをインポート、エクスポートする方法

KibanaのSaved Objects(Dashboard, Search, Visualization)はjson形式でインポート、またはエクスポートすることが出来ます。
Kibana上で実行するのが無難ですが、注意して扱えば一応KibanaのAPIやElasticsearchのAPIを利用する方法もあります。

#環境
Elasticsearch 6.2.4
Kibana 6.2.4

#Kibana上から操作
[公式の手順](https://www.elastic.co/guide/en/kibana/current/managing-saved-objects.html)にある一番無難なやり方です。
Kibana上のManagement > Saved Objects から簡単にインポート、エクスポートすることが出来ます。

###エクスポート
Export Everythingボタンを押して全てのSaved Objectsをエクスポートすることが出来ます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/222603/8d5a2baa-9e86-e0f4-baf4-c7d68061c169.png)

また、Dashboards, Searches, Visualizationsタブから、それぞれ必要な分だけを選択してExportボタンを押すことでエクスポートすることも可能です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/222603/a3dcf7eb-e9b3-9dff-34bb-83023db6e57f.png)

###インポート
Importボタンを押してjsonファイルを選択することでインポートすることが出来ます。
Kibana上からエクスポートしたjsonファイルをそのままインポート可能です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/222603/ecb85158-da6a-4716-588c-88572866747b.png)

#KibanaのAPIを利用
公式の手順ではないですが、KibanaのAPIを利用することでSaved Objectsをインポート、エクスポートすることも可能です。
Kibana上でExportボタンを押した時には裏でこのAPIにリクエストしてjsonファイルを作っているっぽいです。
リクエストする際には以下のリクエストヘッダを指定する必要があります。

- Content-Type: application/json
- kbn-xsrf: true

###エクスポート
APIのエンドポイントは `GET {KibanaのURL}/api/saved_objects/{type}/{id}` です。
typeにはdashboard, search, visualizationのいずれか、idにはSaved Objectsのidを指定します。
どちらも省略することが可能です。
デフォルトでは20件のSaved Objectsを取得できますが、クエリ文字列に`per_page=xxx`を指定することで取得件数を調整することが可能です。  

例えば`localhost:5601/api/saved_objects/dashboard/`にリクエストすると以下のようなjsonがレスポンスされます。

```json
{
  "page": 1,
  "per_page": 20,
  "total": 1,
  "saved_objects": [
    {
      "id": "d9bd3c70-4d20-11e8-8213-eb134e72e4ef",
      "type": "dashboard",
      "updated_at": "2018-05-01T09:20:12.214Z",
      "version": 1,
      "attributes": {
        "title": "Dashboard-Test-1",
        "hits": 0,
        "description": "Test dashboard",
        "panelsJSON": "[{\"gridData\":{\"w\":6,\"h\":3,\"x\":0,\"y\":0,\"i\":\"1\"},\"version\":\"6.2.3\",\"panelIndex\":\"1\",\"type\":\"visualization\",\"id\":\"539a3f30-4d20-11e8-8213-eb134e72e4ef\"},{\"gridData\":{\"w\":6,\"h\":3,\"x\":6,\"y\":0,\"i\":\"2\"},\"version\":\"6.2.3\",\"panelIndex\":\"2\",\"type\":\"visualization\",\"id\":\"a28d6db0-4d20-11e8-8213-eb134e72e4ef\"}]",
        "optionsJSON": "{\"darkTheme\":false,\"useMargins\":true,\"hidePanelTitles\":false}",
        "version": 1,
        "timeRestore": false,
        "kibanaSavedObjectMeta": {
          "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}"
        }
      }
    }
  ]
}
```
レスポンス内のsaved_objectsがSaved Objectsの情報になります。
インポートする際にはsaved_objectsの情報を利用するため、これをバックアップとして取得しておきます。

###インポート
新規作成する場合と更新する場合でAPIのエンドポイントとリクエストボディのjsonが若干異なります。

####新規作成する場合
APIのエンドポイントは `POST {KibanaのURL}/api/saved_objects/{type}/{id}` です。
idは省略可能ですが、省略時は自動的に新しいidが採番されるため、以前保存していたSaved Objectsを復活させたいような場合にはidを指定した方が良いです。

リクエストボディには以下のようなjsonを指定します。

```json
{
  "attributes": {
    "title": "Dashboard-Test-1",
    "hits": 0,
    "description": "Test dashboard",
    "panelsJSON": "[{\"gridData\":{\"w\":6,\"h\":3,\"x\":0,\"y\":0,\"i\":\"1\"},\"version\":\"6.2.3\",\"panelIndex\":\"1\",\"type\":\"visualization\",\"id\":\"539a3f30-4d20-11e8-8213-eb134e72e4ef\"},{\"gridData\":{\"w\":6,\"h\":3,\"x\":6,\"y\":0,\"i\":\"2\"},\"version\":\"6.2.3\",\"panelIndex\":\"2\",\"type\":\"visualization\",\"id\":\"a28d6db0-4d20-11e8-8213-eb134e72e4ef\"}]",
    "optionsJSON": "{\"darkTheme\":false,\"useMargins\":true,\"hidePanelTitles\":false}",
    "version": 1,
    "timeRestore": false,
    "kibanaSavedObjectMeta": {
      "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}"
    }
  }
}
```

エクスポートした時のattributesをそのまま指定するような形になります。

####更新する場合
APIのエンドポイントは `PUT {KibanaのURL}/api/saved_objects/{type}/{id}` です。
指定されたidのSaved Objectsが存在しない場合は404エラーになります。

リクエストボディには以下のようなjsonを指定します。

```json
{
  "version": 1,
  "attributes": {
    "title": "Dashboard-Test-1",
    "hits": 0,
    "description": "Test dashboard",
    "panelsJSON": "[{\"gridData\":{\"w\":6,\"h\":3,\"x\":0,\"y\":0,\"i\":\"1\"},\"version\":\"6.2.3\",\"panelIndex\":\"1\",\"type\":\"visualization\",\"id\":\"539a3f30-4d20-11e8-8213-eb134e72e4ef\"},{\"gridData\":{\"w\":6,\"h\":3,\"x\":6,\"y\":0,\"i\":\"2\"},\"version\":\"6.2.3\",\"panelIndex\":\"2\",\"type\":\"visualization\",\"id\":\"a28d6db0-4d20-11e8-8213-eb134e72e4ef\"}]",
    "optionsJSON": "{\"darkTheme\":false,\"useMargins\":true,\"hidePanelTitles\":false}",
    "version": 1,
    "timeRestore": false,
    "kibanaSavedObjectMeta": {
      "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}"
    }
  }
}
```

エクスポートした時のversionとattributesをそのまま指定するような形になります。

#ElasticsearchのAPIを利用
最終奥義みたいなものですしユーザーの操作は想定されていないと思いますのであまりオススメはしませんが、Elasticsearchに保存されているKibanaの設定情報を直接操作することでインポート、エクスポートすることが可能です。
Kibanaの設定は.kibanaという名前、もしくはエイリアス名のインデックスに保存されているため、これを利用します。

###エクスポート
ElasticsearchのSearch APIを利用します。
エンドポイントは `POST {ElasticsearchのURL}/.kibana/_search` です。
リクエストボディに以下の検索クエリを設定することで、Saved Objectsのデータを取得することが出来ます。

```json:query
{
  "query": {
    "terms": {
      "type": [
        "dashboard",
        "search",
        "visualization"
      ]
    }
  }
}
```

レスポンス内のhitsに以下のようなSaved Objectsの情報が設定されるので、これをバックアップとして取得しておきます。

```json
{
  "hits": [
    {
      "_index": ".kibana",
      "_type": "doc",
      "_id": "dashboard:d9bd3c70-4d20-11e8-8213-eb134e72e4ef",
      "_score": 1.0,
      "_source": {
        "type": "dashboard",
        "updated_at": "2018-05-04T02:05:18.334Z",
        "dashboard": {
          "title": "Dashboard-Test-1",
          "hits": 0,
          "description": "Test dashboard",
          "panelsJSON": "[{\"gridData\":{\"w\":6,\"h\":3,\"x\":0,\"y\":0,\"i\":\"1\"},\"version\":\"6.2.3\",\"panelIndex\":\"1\",\"type\":\"visualization\",\"id\":\"539a3f30-4d20-11e8-8213-eb134e72e4ef\"},{\"gridData\":{\"w\":6,\"h\":3,\"x\":6,\"y\":0,\"i\":\"2\"},\"version\":\"6.2.3\",\"panelIndex\":\"2\",\"type\":\"visualization\",\"id\":\"a28d6db0-4d20-11e8-8213-eb134e72e4ef\"}]",
          "optionsJSON": "{\"darkTheme\":false,\"useMargins\":true,\"hidePanelTitles\":false}",
          "version": 1,
          "timeRestore": false,
          "kibanaSavedObjectMeta": {
            "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}"
          }
        }
      }
    }
  ]
}
```

###インポート
ElasticsearchのIndex APIを利用します。
エンドポイントは `PUT {ElasticsearchのURL}/.kibana/{_type}/{_id}` です。
\_typeと\_idにはそれぞれエクスポートした際の値を設定します。
リクエストボディにはエクスポートした際の\_sourceの中身をそのまま設定します。

```json
{
  "type": "dashboard",
  "updated_at": "2018-05-04T02:05:18.334Z",
  "dashboard": {
    "title": "Dashboard-Test-1",
    "hits": 0,
    "description": "Test dashboard",
    "panelsJSON": "[{\"gridData\":{\"w\":6,\"h\":3,\"x\":0,\"y\":0,\"i\":\"1\"},\"version\":\"6.2.3\",\"panelIndex\":\"1\",\"type\":\"visualization\",\"id\":\"539a3f30-4d20-11e8-8213-eb134e72e4ef\"},{\"gridData\":{\"w\":6,\"h\":3,\"x\":6,\"y\":0,\"i\":\"2\"},\"version\":\"6.2.3\",\"panelIndex\":\"2\",\"type\":\"visualization\",\"id\":\"a28d6db0-4d20-11e8-8213-eb134e72e4ef\"}]",
    "optionsJSON": "{\"darkTheme\":false,\"useMargins\":true,\"hidePanelTitles\":false}",
    "version": 1,
    "timeRestore": false,
    "kibanaSavedObjectMeta": {
      "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}"
    }
  }
}
```