From 66939bf2ee7cf252bff16a64edffa2a563d4dd21 Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Sun, 21 Jun 2015 10:11:47 +0000 Subject: [PATCH 1/2] add PullImageWithOutput --- dockerclient.go | 41 ++++++++++++++++++++++++++++++++--------- dockerclient_test.go | 21 ++++++++++++++++----- interface.go | 2 +- mockclient/mock.go | 4 ++-- 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/dockerclient.go b/dockerclient.go index c4302d5..8bd1e6d 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -14,6 +14,9 @@ import ( "strings" "sync/atomic" "time" + + "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/docker/pkg/term" ) const ( @@ -525,11 +528,11 @@ func (client *DockerClient) Version() (*Version, error) { return version, nil } -func (client *DockerClient) PullImage(name string, auth *AuthConfig) error { +func (client *DockerClient) PullImage(name string, auth *AuthConfig, cliOut io.Writer) (err error) { v := url.Values{} v.Set("fromImage", name) uri := fmt.Sprintf("/%s/images/create?%s", APIVersion, v.Encode()) - req, err := http.NewRequest("POST", client.URL.String()+uri, nil) + req, _ := http.NewRequest("POST", client.URL.String()+uri, nil) if auth != nil { encoded_auth, err := auth.encode() if err != nil { @@ -537,9 +540,10 @@ func (client *DockerClient) PullImage(name string, auth *AuthConfig) error { } req.Header.Add("X-Registry-Auth", encoded_auth) } - resp, err := client.HTTPClient.Do(req) + var resp *http.Response + resp, err = client.HTTPClient.Do(req) if err != nil { - return err + return } defer resp.Body.Close() @@ -554,16 +558,35 @@ func (client *DockerClient) PullImage(name string, auth *AuthConfig) error { return fmt.Errorf("%s", string(data)) } + errorReader := io.Reader(resp.Body) + if cliOut != nil { + pipeReader, pipeWriter := io.Pipe() + streamErrChan := make(chan error) + defer func() { + pipeWriter.Close() + if err == nil { + err = <-streamErrChan + } + }() + errorReader = io.TeeReader(resp.Body, pipeWriter) + go func() { + fd, isTerminalIn := term.GetFdInfo(cliOut) + streamErrChan <- jsonmessage.DisplayJSONMessagesStream(pipeReader, cliOut, fd, isTerminalIn) + }() + } var finalObj map[string]interface{} - for decoder := json.NewDecoder(resp.Body); err == nil; err = decoder.Decode(&finalObj) { + for decoder := json.NewDecoder(errorReader); err == nil; err = decoder.Decode(&finalObj) { } if err != io.EOF { - return err + return + } else { + err = nil } - if err, ok := finalObj["error"]; ok { - return fmt.Errorf("%v", err) + if errObj, ok := finalObj["error"]; ok { + err = fmt.Errorf("%v", errObj) + return } - return nil + return } func (client *DockerClient) InspectImage(id string) (*ImageInfo, error) { diff --git a/dockerclient_test.go b/dockerclient_test.go index afc1b07..c2f3019 100644 --- a/dockerclient_test.go +++ b/dockerclient_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "os" "reflect" "strings" "testing" @@ -31,6 +32,16 @@ func testDockerClient(t *testing.T) *DockerClient { return client } +func ExampleDockerClient_PullImage() { + docker, err := NewDockerClient("unix:///var/run/docker.sock", nil) + if err != nil { + panic(err) + } + if err := docker.PullImage("busybox", nil, os.Stdout); err != nil { + panic(err) + } +} + func TestInfo(t *testing.T) { client := testDockerClient(t) info, err := client.Info() @@ -70,17 +81,17 @@ func TestWait(t *testing.T) { func TestPullImage(t *testing.T) { client := testDockerClient(t) - err := client.PullImage("busybox", nil) + err := client.PullImage("busybox", nil, nil) if err != nil { - t.Fatal("unable to pull busybox") + t.Fatal("unable to pull busybox: %v", err) } - err = client.PullImage("haproxy", nil) + err = client.PullImage("haproxy", nil, nil) if err != nil { - t.Fatal("unable to pull haproxy") + t.Fatal("unable to pull haproxy: %v", err) } - err = client.PullImage("wrongimg", nil) + err = client.PullImage("wrongimg", nil, nil) if err == nil { t.Fatal("should return error when it fails to pull wrongimg") } diff --git a/interface.go b/interface.go index 4215ca4..d19240b 100644 --- a/interface.go +++ b/interface.go @@ -35,7 +35,7 @@ type Client interface { StopAllMonitorStats() TagImage(nameOrID string, repo string, tag string, force bool) error Version() (*Version, error) - PullImage(name string, auth *AuthConfig) error + PullImage(name string, auth *AuthConfig, cliOut io.Writer) error LoadImage(reader io.Reader) error RemoveContainer(id string, force, volumes bool) error ListImages(all bool) ([]*Image, error) diff --git a/mockclient/mock.go b/mockclient/mock.go index 9944512..0236f20 100644 --- a/mockclient/mock.go +++ b/mockclient/mock.go @@ -106,8 +106,8 @@ func (client *MockClient) Version() (*dockerclient.Version, error) { return args.Get(0).(*dockerclient.Version), args.Error(1) } -func (client *MockClient) PullImage(name string, auth *dockerclient.AuthConfig) error { - args := client.Mock.Called(name, auth) +func (client *MockClient) PullImage(name string, auth *dockerclient.AuthConfig, cliOut io.Writer) error { + args := client.Mock.Called(name, auth, cliOut) return args.Error(0) } From 9104cde15949f6c7a7df740f4d98e6a2df2c1ea0 Mon Sep 17 00:00:00 2001 From: Ivan Mikushin Date: Wed, 7 Oct 2015 18:20:42 +0500 Subject: [PATCH 2/2] Report pull progress to an optionally provided io.Writer Does not break existing clients, who are not interested in pull progress reporting and are not providing the io.Writer --- dockerclient.go | 6 +++++- dockerclient_test.go | 6 +++--- interface.go | 2 +- mockclient/mock.go | 4 ++-- nopclient/nop.go | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/dockerclient.go b/dockerclient.go index 8bd1e6d..7e900c7 100644 --- a/dockerclient.go +++ b/dockerclient.go @@ -528,7 +528,7 @@ func (client *DockerClient) Version() (*Version, error) { return version, nil } -func (client *DockerClient) PullImage(name string, auth *AuthConfig, cliOut io.Writer) (err error) { +func (client *DockerClient) PullImage(name string, auth *AuthConfig, out ...io.Writer) (err error) { v := url.Values{} v.Set("fromImage", name) uri := fmt.Sprintf("/%s/images/create?%s", APIVersion, v.Encode()) @@ -559,6 +559,10 @@ func (client *DockerClient) PullImage(name string, auth *AuthConfig, cliOut io.W } errorReader := io.Reader(resp.Body) + var cliOut io.Writer + if len(out) > 0 { + cliOut = out[0] + } if cliOut != nil { pipeReader, pipeWriter := io.Pipe() streamErrChan := make(chan error) diff --git a/dockerclient_test.go b/dockerclient_test.go index c2f3019..1ca0be3 100644 --- a/dockerclient_test.go +++ b/dockerclient_test.go @@ -81,17 +81,17 @@ func TestWait(t *testing.T) { func TestPullImage(t *testing.T) { client := testDockerClient(t) - err := client.PullImage("busybox", nil, nil) + err := client.PullImage("busybox", nil) // old clients do not break on the API change if err != nil { t.Fatal("unable to pull busybox: %v", err) } - err = client.PullImage("haproxy", nil, nil) + err = client.PullImage("haproxy", nil, os.Stderr) // io.Writer is optional if err != nil { t.Fatal("unable to pull haproxy: %v", err) } - err = client.PullImage("wrongimg", nil, nil) + err = client.PullImage("wrongimg", nil) if err == nil { t.Fatal("should return error when it fails to pull wrongimg") } diff --git a/interface.go b/interface.go index d19240b..c67c9a3 100644 --- a/interface.go +++ b/interface.go @@ -35,7 +35,7 @@ type Client interface { StopAllMonitorStats() TagImage(nameOrID string, repo string, tag string, force bool) error Version() (*Version, error) - PullImage(name string, auth *AuthConfig, cliOut io.Writer) error + PullImage(name string, auth *AuthConfig, out ...io.Writer) error LoadImage(reader io.Reader) error RemoveContainer(id string, force, volumes bool) error ListImages(all bool) ([]*Image, error) diff --git a/mockclient/mock.go b/mockclient/mock.go index 0236f20..1b80954 100644 --- a/mockclient/mock.go +++ b/mockclient/mock.go @@ -106,8 +106,8 @@ func (client *MockClient) Version() (*dockerclient.Version, error) { return args.Get(0).(*dockerclient.Version), args.Error(1) } -func (client *MockClient) PullImage(name string, auth *dockerclient.AuthConfig, cliOut io.Writer) error { - args := client.Mock.Called(name, auth, cliOut) +func (client *MockClient) PullImage(name string, auth *dockerclient.AuthConfig, out ...io.Writer) error { + args := client.Mock.Called(name, auth, out...) return args.Error(0) } diff --git a/nopclient/nop.go b/nopclient/nop.go index ef64536..7083b77 100644 --- a/nopclient/nop.go +++ b/nopclient/nop.go @@ -94,7 +94,7 @@ func (client *NopClient) Version() (*dockerclient.Version, error) { return nil, ErrNoEngine } -func (client *NopClient) PullImage(name string, auth *dockerclient.AuthConfig) error { +func (client *NopClient) PullImage(name string, auth *dockerclient.AuthConfig, out ...io.Writer) error { return ErrNoEngine }