etcd を go から使う


## 概要
オフィシャルクライアントライブラリを使って etcd を go から触るという話。

## 準備
まず etcd を用意する。 Virtualbox と Vagrant があれば、[coreos-vagrant](https://github.com/coreos/coreos-vagrant/) を使ってローカルに CoreOS マシンを立てることで、`http://172.17.8.101:4001` に etcd のエンドポイントができる。

[Vagrant(Virtualbox) 上で CoreOS を動かす](http://qiita.com/spesnova/items/17c3702b22f332352c9c) を参照。

次 go-etcd とってくる

```bash
$ go get coreos/go-etcd
```

## go から etcd を触る

```go
package main

import (
	"fmt"
	"github.com/coreos/go-etcd/etcd"
)

// list of etcd endpoint
var machines = []string{
	"http://172.17.8.101:4001",
}

func main() {
	// Create etcd client
	client := etcd.NewClient(machines)

	// SET "japan" to /country/jp
	if _, err := client.Set("/country/jp", "japan", 0); err == nil {
		fmt.Println("Set /country/jp") 
	} else {
		fmt.Println("error")
	}

	// SET "United States of Amarica" to /country/us
	if _, err := client.Set("/country/us", "United States of Amarica", 0); err == nil {
		fmt.Println("Set /country/us")
	} else {
		fmt.Println("error")
	}

	// GET /country/jp
	if res, err := client.Get("/country/jp", true, false); err == nil {
		fmt.Println("key:", res.Node.Key) // => key: /country/jp
		fmt.Println("value:", res.Node.Value) // => value: japan
	} else {
		fmt.Println("error")
	}

	// GET /country as raw data
	if res, err := client.RawGet("/country/jp", true, false); err == nil {
		fmt.Println(res.StatusCode) // => 200
		fmt.Println(string(res.Body)) // => {"action":"get","node":{"key":"/country/jp","value":"japan","modifiedIndex":5935,"createdIndex":5935}}
	} else {
		fmt.Println("error")
	}

	// GET /country as raw data
	if res, err := client.RawGet("/country", true, false); err == nil {
		fmt.Println(res.StatusCode) // => 200
		fmt.Println(string(res.Body)) // => {"action":"get","node":{"key":"/country","dir":true,"nodes":[{"key":"/country/jp","value":"japan","modifiedIndex":5935,"createdIndex":5935},{"key":"/country/us","value":"United States of Amarica","modifiedIndex":5936,"createdIndex":5936}],"modifiedIndex":4817,"createdIndex":4817}}
	} else {
		fmt.Println("error")
	}
}
```

### 再起的に取得
以下のようなサービスディスカバリ情報が `/services` 以下にあるとき、
（ちなみにこの `/services` は [CoreOS 上の Docker コンテナのサービス情報を registrator を使って etcd へ自動登録する](http://qiita.com/spesnova/items/ab1c5ccc996f8d681839) でセットされてるもの）

```bash
core@core-01 ~ $ etcdctl ls --recursive /services
/services/logspout
/services/logspout/core-01:logspout:8000
/services/logspout/core-02:logspout:8000
/services/logspout/core-03:logspout:8000
/services/registry
/services/registry/core-01:registry:5000
/services/registry/core-02:registry:5000
/services/registry/core-03:registry:5000
```

以下のように、`RawGet` の 3 つ目の引数（`recursive` オプション) に `true` を渡すことで

```go
	// GET /services as raw data
	if res, err := client.RawGet("/services", true, true); err == nil {
		fmt.Println(res.StatusCode)
		fmt.Println(string(res.Body))
	} else {
		fmt.Println("error")
	}
```

`/services` 以下のデータが全て取得できる

```json
{
  "node": {
    "createdIndex": 268,
    "modifiedIndex": 268,
    "nodes": [
      {
        "createdIndex": 313,
        "modifiedIndex": 313,
        "nodes": [
          {
            "createdIndex": 4760,
            "modifiedIndex": 4760,
            "value": "172.17.8.101:8000",
            "key": "/services/logspout/core-01:logspout:8000"
          },
          {
            "createdIndex": 4552,
            "modifiedIndex": 4552,
            "value": "172.17.8.102:8000",
            "key": "/services/logspout/core-02:logspout:8000"
          },
          {
            "createdIndex": 4579,
            "modifiedIndex": 4579,
            "value": "172.17.8.103:8000",
            "key": "/services/logspout/core-03:logspout:8000"
          }
        ],
        "dir": true,
        "key": "/services/logspout"
      },
      {
        "createdIndex": 268,
        "modifiedIndex": 268,
        "nodes": [
          {
            "createdIndex": 4761,
            "modifiedIndex": 4761,
            "value": "172.17.8.101:5000",
            "key": "/services/registry/core-01:registry:5000"
          },
          {
            "createdIndex": 4554,
            "modifiedIndex": 4554,
            "value": "172.17.8.102:5000",
            "key": "/services/registry/core-02:registry:5000"
          },
          {
            "createdIndex": 4581,
            "modifiedIndex": 4581,
            "value": "172.17.8.103:5000",
            "key": "/services/registry/core-03:registry:5000"
          }
        ],
        "dir": true,
        "key": "/services/registry"
      }
    ],
    "dir": true,
    "key": "/services"
  },
  "action": "get"
}
```

## メモと感想
### 他にできること

* TLC クライアントを作成する `NewTLSClient`
* etcd の変更を long polling で監視する `Watch`
* etcd クラスタ情報の取得する `GetCluster`

などなど。

詳細は [go-etcd - GoDoc](https://godoc.org/github.com/coreos/go-etcd/etcd) を参照。


## REF

* [coreos/go-etcd](https://github.com/coreos/go-etcd)
* [coreos/etcd](https://github.com/coreos/etcd)
* [etcd API](https://coreos.com/docs/distributed-configuration/etcd-api/)
* [coreos/coreos-vagrant](https://github.com/coreos/coreos-vagrant/)