Skip to content

Commit

Permalink
container: fix Logs behavior
Browse files Browse the repository at this point in the history
Docker sends the output in the following format:

	<header identifying the stream and with the size fo the frame>
	<frame>

It allows us to multiplex the content between the output and error
streams.

I wondering whether I should rewrite the StdCopy function, it's a little
messy.

Related to #114.
  • Loading branch information
fsouza committed Jul 9, 2014
1 parent f803c97 commit b4230ff
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 14 deletions.
23 changes: 14 additions & 9 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down
8 changes: 6 additions & 2 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
34 changes: 34 additions & 0 deletions container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}))
Expand Down Expand Up @@ -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
}))
Expand Down Expand Up @@ -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{})
Expand Down
6 changes: 3 additions & 3 deletions image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit b4230ff

Please sign in to comment.