diff --git a/client.go b/client.go index 82f15d68..ccc8de7d 100644 --- a/client.go +++ b/client.go @@ -280,11 +280,10 @@ func (c *Client) do(method, path string, data interface{}) ([]byte, int, error) return body, resp.StatusCode, nil } -func (c *Client) stream(method, path string, headers map[string]string, in io.Reader, out io.Writer) error { +func (c *Client) stream(method, path string, setRawTerminal bool, headers map[string]string, in io.Reader, stdout, stderr io.Writer) error { if (method == "POST" || method == "PUT") && in == nil { in = bytes.NewReader(nil) } - if path != "/version" && !c.SkipServerVersionCheck && c.expectedApiVersion == nil { err := c.checkApiVersion() if err != nil { @@ -305,8 +304,11 @@ func (c *Client) stream(method, path string, headers map[string]string, in io.Re var resp *http.Response protocol := c.endpointURL.Scheme address := c.endpointURL.Path - if out == nil { - out = ioutil.Discard + if stdout == nil { + stdout = ioutil.Discard + } + if stderr == nil { + stderr = ioutil.Discard } if protocol == "unix" { dial, err := net.Dial(protocol, address) @@ -343,20 +345,23 @@ func (c *Client) stream(method, path string, headers map[string]string, in io.Re return err } if m.Stream != "" { - fmt.Fprint(out, m.Stream) + fmt.Fprint(stdout, m.Stream) } else if m.Progress != "" { - fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress) + fmt.Fprintf(stdout, "%s %s\r", m.Status, m.Progress) } else if m.Error != "" { return errors.New(m.Error) } if m.Status != "" { - fmt.Fprintln(out, m.Status) + fmt.Fprintln(stdout, m.Status) } } } else { - if _, err := io.Copy(out, resp.Body); err != nil { - return err + if setRawTerminal { + _, err = io.Copy(stdout, resp.Body) + } else { + _, err = utils.StdCopy(stdout, stderr, resp.Body) } + return err } return nil } diff --git a/container.go b/container.go index bcb9b173..074addb3 100644 --- a/container.go +++ b/container.go @@ -568,11 +568,15 @@ func (c *Client) AttachToContainer(opts AttachToContainerOptions) error { type LogsOptions struct { Container string `qs:"-"` OutputStream io.Writer `qs:"-"` + ErrorStream io.Writer `qs:"-"` Follow bool Stdout bool Stderr bool Timestamps bool Tail string + + // Use raw terminal? Usually true when the container contains a TTY. + RawTerminal bool `qs:"-"` } // Logs gets stdout and stderr logs from the specified container. @@ -586,7 +590,7 @@ func (c *Client) Logs(opts LogsOptions) error { opts.Tail = "all" } path := "/containers/" + opts.Container + "/logs?" + queryString(opts) - return c.stream("GET", path, nil, nil, opts.OutputStream) + return c.stream("GET", path, opts.RawTerminal, nil, nil, opts.OutputStream, opts.ErrorStream) } // ResizeContainerTTY resizes the terminal to the given height and width. @@ -616,7 +620,7 @@ func (c *Client) ExportContainer(opts ExportContainerOptions) error { return NoSuchContainer{ID: opts.ID} } url := fmt.Sprintf("/containers/%s/export", opts.ID) - return c.stream("GET", url, nil, nil, opts.OutputStream) + return c.stream("GET", url, true, nil, nil, opts.OutputStream, nil) } // NoSuchContainer is the error returned when a given container does not exist. diff --git a/container_test.go b/container_test.go index 61aee545..4a0829a1 100644 --- a/container_test.go +++ b/container_test.go @@ -912,6 +912,8 @@ func TestAttachToContainerWithoutContainer(t *testing.T) { func TestLogs(t *testing.T) { var req http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19} + w.Write(prefix) w.Write([]byte("something happened!")) req = *r })) @@ -958,6 +960,8 @@ func TestLogs(t *testing.T) { func TestLogsSpecifyingTail(t *testing.T) { var req http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19} + w.Write(prefix) w.Write([]byte("something happened!")) req = *r })) @@ -1002,6 +1006,36 @@ func TestLogsSpecifyingTail(t *testing.T) { } } +func TestLogsRawTerminal(t *testing.T) { + var req http.Request + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("something happened!")) + req = *r + })) + defer server.Close() + client, _ := NewClient(server.URL) + client.SkipServerVersionCheck = true + var buf bytes.Buffer + opts := LogsOptions{ + Container: "a123456", + OutputStream: &buf, + Follow: true, + RawTerminal: true, + Stdout: true, + Stderr: true, + Timestamps: true, + Tail: "100", + } + err := client.Logs(opts) + if err != nil { + t.Fatal(err) + } + expected := "something happened!" + if buf.String() != expected { + t.Errorf("Logs: wrong output. Want %q. Got %q.", expected, buf.String()) + } +} + func TestLogsNoContainer(t *testing.T) { var client Client err := client.Logs(LogsOptions{}) diff --git a/image.go b/image.go index 3350e98e..a94cf553 100644 --- a/image.go +++ b/image.go @@ -187,7 +187,7 @@ func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes()) - return c.stream("POST", path, headers, nil, opts.OutputStream) + return c.stream("POST", path, true, headers, nil, opts.OutputStream, nil) } // PullImageOptions present the set of options available for pulling an image @@ -219,7 +219,7 @@ func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer) error { path := "/images/create?" + qs - return c.stream("POST", path, headers, in, w) + return c.stream("POST", path, true, headers, in, w, nil) } // ImportImageOptions present the set of informations available for importing @@ -286,7 +286,7 @@ func (c *Client) BuildImage(opts BuildImageOptions) error { return ErrMissingRepo } return c.stream("POST", fmt.Sprintf("/build?%s", - queryString(&opts)), headers, opts.InputStream, opts.OutputStream) + queryString(&opts)), true, headers, opts.InputStream, opts.OutputStream, nil) } // TagImageOptions present the set of options to tag an image