DRYな備忘録

Don't Repeat Yourself.

GoのコードからDockerイメージのpullを実装する(bufio.Scannerかわいい)

前回エントリ↓でコードからのイメージのpullが動かなくてあっれおかしーなーとなって悔しかったのでリベンジです。

otiai10.hatenablog.com

tl;dr

  • client.ImagePullの返り値はio.ReadCloser型とerror
  • このio.ReadCloserが、イメージのpullのprogressなどを表すHTTPのストリーム
  • このストリームへの書き込みが終わる(つまりdocker pullが完了する)まで待つ必要があった

以下読まなくてよいです

動かないコード

func main() {

    c, err := client.NewEnvClient()
    if err != nil {
        panic(fmt.Errorf("NewEnvClient: %v", err))
    }
    ctx := context.Background()

    // "debian"(:latestは省略可)のpullを試みる
    rp, err := c.ImagePull(ctx, "debian", types.ImagePullOptions{})
    if err != nil {
        panic(fmt.Errorf("ImageSave: %v", err))
    }
    defer rp.Close()
    // エラーさえ無ければイメージのpullが成功していると誤解していた

    cc := &container.Config{Image: "debian"}
    hc := &container.HostConfig{AutoRemove: true}
    nc := &network.NetworkingConfig{}
    body, err := c.ContainerCreate(ctx, cc, hc, nc, "bar")
    if err != nil {
        panic(fmt.Errorf("ContainerCreate: %v", err))
        // 結局ここで"debianなんてイメージは無い!"と叱られる
    }
    fmt.Printf("Created Container:\n%+v\n", body)

}

動くコード

前後省略します

func main() {

    /* 省略(クライアントの初期化とかする) */

    // "debian"(:latestは省略可)のpullを試みる
    rp, err := c.ImagePull(ctx, "debian", types.ImagePullOptions{})
    if err != nil {
        panic(fmt.Errorf("ImageSave: %v", err))
    }
    defer rp.Close()

    // {{{ こっからが味噌!!

    // こういう構造のバッファが書き込まれる
    payload := struct {
        ID             string `json:"id"`
        Status         string `json:"status"`
        Progress       string `json:"progress"`
        ProgressDetail struct {
            Current uint16 `json:"current"`
            Total   uint16 `json:"total"`
        } `json:"progressDetail"`
    }{}

    // bufio.Scannerマジでかわいい
    scanner := bufio.NewScanner(rp)
    for scanner.Scan() {
        json.Unmarshal(scanner.Bytes(), &payload)
        fmt.Printf("\t%+v\n", payload)
    }

    // }}} ここまでが味噌!!

    /* 省略(得られたイメージを指定してコンテナつくったりする) */

}

上記では、bufio.Scannerを使ってHTTPのEOFまで待っている。ついでに、毎バッファ書き込みされるレスポンスを取り出して上げている。
↓こういうきれいな感じのペイロードが送られてくる。

f:id:otiai10:20170526152637p:plain

Scanner使わないんだったらioutil.ReadAllかなー他にストリームが終わるの同期的に待つのどうすっかなーと思ってググりました。

今回のように毎ペイロードが独立して意味のあるものであれば、Scanner使うのが良い気がするものの、もっと良い(単にReaderのEOFを待つための)方法があれば教えてくださいm(_ _)m

雑感

  • Goのコードからgithub.com/docker/docker/clientを使って (1) イメージのpull (2) コンテナ起動 が実現されたので、これでほぼ心置きなく俺々Dockerクライアントを自作できますね!やったね!
  • 腰椎椎間板ヘルニアになっちゃった。腰痛ぇ。

DRYな備忘録として

Docker

Docker

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス