| ... | ... |
@@ -557,43 +557,26 @@ func getContainersLogs(eng *engine.Engine, version version.Version, w http.Respo |
| 557 | 557 |
return fmt.Errorf("Missing parameter")
|
| 558 | 558 |
} |
| 559 | 559 |
|
| 560 |
- var ( |
|
| 561 |
- inspectJob = eng.Job("container_inspect", vars["name"])
|
|
| 562 |
- logsJob = eng.Job("logs", vars["name"])
|
|
| 563 |
- c, err = inspectJob.Stdout.AddEnv() |
|
| 564 |
- ) |
|
| 565 |
- if err != nil {
|
|
| 566 |
- return err |
|
| 567 |
- } |
|
| 568 |
- logsJob.Setenv("follow", r.Form.Get("follow"))
|
|
| 569 |
- logsJob.Setenv("tail", r.Form.Get("tail"))
|
|
| 570 |
- logsJob.Setenv("stdout", r.Form.Get("stdout"))
|
|
| 571 |
- logsJob.Setenv("stderr", r.Form.Get("stderr"))
|
|
| 572 |
- logsJob.Setenv("timestamps", r.Form.Get("timestamps"))
|
|
| 573 | 560 |
// Validate args here, because we can't return not StatusOK after job.Run() call |
| 574 |
- stdout, stderr := logsJob.GetenvBool("stdout"), logsJob.GetenvBool("stderr")
|
|
| 561 |
+ stdout, stderr := toBool(r.Form.Get("stdout")), toBool(r.Form.Get("stderr"))
|
|
| 575 | 562 |
if !(stdout || stderr) {
|
| 576 | 563 |
return fmt.Errorf("Bad parameters: you must choose at least one stream")
|
| 577 | 564 |
} |
| 578 |
- if err = inspectJob.Run(); err != nil {
|
|
| 579 |
- return err |
|
| 580 |
- } |
|
| 581 | 565 |
|
| 582 |
- var outStream, errStream io.Writer |
|
| 583 |
- outStream = utils.NewWriteFlusher(w) |
|
| 584 |
- |
|
| 585 |
- if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") {
|
|
| 586 |
- errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) |
|
| 587 |
- outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) |
|
| 588 |
- } else {
|
|
| 589 |
- errStream = outStream |
|
| 566 |
+ logsConfig := &daemon.ContainerLogsConfig{
|
|
| 567 |
+ Follow: toBool(r.Form.Get("follow")),
|
|
| 568 |
+ Timestamps: toBool(r.Form.Get("timestamps")),
|
|
| 569 |
+ Tail: r.Form.Get("tail"),
|
|
| 570 |
+ UseStdout: stdout, |
|
| 571 |
+ UseStderr: stderr, |
|
| 572 |
+ OutStream: utils.NewWriteFlusher(w), |
|
| 590 | 573 |
} |
| 591 | 574 |
|
| 592 |
- logsJob.Stdout.Add(outStream) |
|
| 593 |
- logsJob.Stderr.Set(errStream) |
|
| 594 |
- if err := logsJob.Run(); err != nil {
|
|
| 595 |
- fmt.Fprintf(outStream, "Error running logs job: %s\n", err) |
|
| 575 |
+ d := getDaemon(eng) |
|
| 576 |
+ if err := d.ContainerLogs(vars["name"], logsConfig); err != nil {
|
|
| 577 |
+ fmt.Fprintf(w, "Error running logs job: %s\n", err) |
|
| 596 | 578 |
} |
| 579 |
+ |
|
| 597 | 580 |
return nil |
| 598 | 581 |
} |
| 599 | 582 |
|
| ... | ... |
@@ -7,7 +7,6 @@ import ( |
| 7 | 7 |
"io" |
| 8 | 8 |
"net/http" |
| 9 | 9 |
"net/http/httptest" |
| 10 |
- "strings" |
|
| 11 | 10 |
"testing" |
| 12 | 11 |
|
| 13 | 12 |
"github.com/docker/docker/api" |
| ... | ... |
@@ -126,99 +125,6 @@ func TestGetContainersByName(t *testing.T) {
|
| 126 | 126 |
} |
| 127 | 127 |
} |
| 128 | 128 |
|
| 129 |
-func TestLogs(t *testing.T) {
|
|
| 130 |
- eng := engine.New() |
|
| 131 |
- var inspect bool |
|
| 132 |
- var logs bool |
|
| 133 |
- eng.Register("container_inspect", func(job *engine.Job) error {
|
|
| 134 |
- inspect = true |
|
| 135 |
- if len(job.Args) == 0 {
|
|
| 136 |
- t.Fatal("Job arguments is empty")
|
|
| 137 |
- } |
|
| 138 |
- if job.Args[0] != "test" {
|
|
| 139 |
- t.Fatalf("Container name %s, must be test", job.Args[0])
|
|
| 140 |
- } |
|
| 141 |
- return nil |
|
| 142 |
- }) |
|
| 143 |
- expected := "logs" |
|
| 144 |
- eng.Register("logs", func(job *engine.Job) error {
|
|
| 145 |
- logs = true |
|
| 146 |
- if len(job.Args) == 0 {
|
|
| 147 |
- t.Fatal("Job arguments is empty")
|
|
| 148 |
- } |
|
| 149 |
- if job.Args[0] != "test" {
|
|
| 150 |
- t.Fatalf("Container name %s, must be test", job.Args[0])
|
|
| 151 |
- } |
|
| 152 |
- follow := job.Getenv("follow")
|
|
| 153 |
- if follow != "1" {
|
|
| 154 |
- t.Fatalf("follow: %s, must be 1", follow)
|
|
| 155 |
- } |
|
| 156 |
- stdout := job.Getenv("stdout")
|
|
| 157 |
- if stdout != "1" {
|
|
| 158 |
- t.Fatalf("stdout %s, must be 1", stdout)
|
|
| 159 |
- } |
|
| 160 |
- stderr := job.Getenv("stderr")
|
|
| 161 |
- if stderr != "" {
|
|
| 162 |
- t.Fatalf("stderr %s, must be empty", stderr)
|
|
| 163 |
- } |
|
| 164 |
- timestamps := job.Getenv("timestamps")
|
|
| 165 |
- if timestamps != "1" {
|
|
| 166 |
- t.Fatalf("timestamps %s, must be 1", timestamps)
|
|
| 167 |
- } |
|
| 168 |
- job.Stdout.Write([]byte(expected)) |
|
| 169 |
- return nil |
|
| 170 |
- }) |
|
| 171 |
- r := serveRequest("GET", "/containers/test/logs?follow=1&stdout=1×tamps=1", nil, eng, t)
|
|
| 172 |
- if r.Code != http.StatusOK {
|
|
| 173 |
- t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK)
|
|
| 174 |
- } |
|
| 175 |
- if !inspect {
|
|
| 176 |
- t.Fatal("container_inspect job was not called")
|
|
| 177 |
- } |
|
| 178 |
- if !logs {
|
|
| 179 |
- t.Fatal("logs job was not called")
|
|
| 180 |
- } |
|
| 181 |
- res := r.Body.String() |
|
| 182 |
- if res != expected {
|
|
| 183 |
- t.Fatalf("Output %s, expected %s", res, expected)
|
|
| 184 |
- } |
|
| 185 |
-} |
|
| 186 |
- |
|
| 187 |
-func TestLogsNoStreams(t *testing.T) {
|
|
| 188 |
- eng := engine.New() |
|
| 189 |
- var inspect bool |
|
| 190 |
- var logs bool |
|
| 191 |
- eng.Register("container_inspect", func(job *engine.Job) error {
|
|
| 192 |
- inspect = true |
|
| 193 |
- if len(job.Args) == 0 {
|
|
| 194 |
- t.Fatal("Job arguments is empty")
|
|
| 195 |
- } |
|
| 196 |
- if job.Args[0] != "test" {
|
|
| 197 |
- t.Fatalf("Container name %s, must be test", job.Args[0])
|
|
| 198 |
- } |
|
| 199 |
- return nil |
|
| 200 |
- }) |
|
| 201 |
- eng.Register("logs", func(job *engine.Job) error {
|
|
| 202 |
- logs = true |
|
| 203 |
- return nil |
|
| 204 |
- }) |
|
| 205 |
- r := serveRequest("GET", "/containers/test/logs", nil, eng, t)
|
|
| 206 |
- if r.Code != http.StatusBadRequest {
|
|
| 207 |
- t.Fatalf("Got status %d, expected %d", r.Code, http.StatusBadRequest)
|
|
| 208 |
- } |
|
| 209 |
- if inspect {
|
|
| 210 |
- t.Fatal("container_inspect job was called, but it shouldn't")
|
|
| 211 |
- } |
|
| 212 |
- if logs {
|
|
| 213 |
- t.Fatal("logs job was called, but it shouldn't")
|
|
| 214 |
- } |
|
| 215 |
- res := strings.TrimSpace(r.Body.String()) |
|
| 216 |
- expected := "Bad parameters: you must choose at least one stream" |
|
| 217 |
- if !strings.Contains(res, expected) {
|
|
| 218 |
- t.Fatalf("Output %s, expected %s in it", res, expected)
|
|
| 219 |
- } |
|
| 220 |
-} |
|
| 221 |
- |
|
| 222 | 129 |
func TestGetImagesByName(t *testing.T) {
|
| 223 | 130 |
eng := engine.New() |
| 224 | 131 |
name := "image_name" |
| ... | ... |
@@ -123,7 +123,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
|
| 123 | 123 |
"create": daemon.ContainerCreate, |
| 124 | 124 |
"export": daemon.ContainerExport, |
| 125 | 125 |
"info": daemon.CmdInfo, |
| 126 |
- "logs": daemon.ContainerLogs, |
|
| 127 | 126 |
"restart": daemon.ContainerRestart, |
| 128 | 127 |
"start": daemon.ContainerStart, |
| 129 | 128 |
"execCreate": daemon.ContainerExecCreate, |
| ... | ... |
@@ -10,40 +10,50 @@ import ( |
| 10 | 10 |
"sync" |
| 11 | 11 |
|
| 12 | 12 |
"github.com/Sirupsen/logrus" |
| 13 |
- "github.com/docker/docker/engine" |
|
| 14 | 13 |
"github.com/docker/docker/pkg/jsonlog" |
| 14 |
+ "github.com/docker/docker/pkg/stdcopy" |
|
| 15 | 15 |
"github.com/docker/docker/pkg/tailfile" |
| 16 | 16 |
"github.com/docker/docker/pkg/timeutils" |
| 17 | 17 |
) |
| 18 | 18 |
|
| 19 |
-func (daemon *Daemon) ContainerLogs(job *engine.Job) error {
|
|
| 20 |
- if len(job.Args) != 1 {
|
|
| 21 |
- return fmt.Errorf("Usage: %s CONTAINER\n", job.Name)
|
|
| 22 |
- } |
|
| 19 |
+type ContainerLogsConfig struct {
|
|
| 20 |
+ Follow, Timestamps bool |
|
| 21 |
+ Tail string |
|
| 22 |
+ UseStdout, UseStderr bool |
|
| 23 |
+ OutStream io.Writer |
|
| 24 |
+} |
|
| 23 | 25 |
|
| 26 |
+func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) error {
|
|
| 24 | 27 |
var ( |
| 25 |
- name = job.Args[0] |
|
| 26 |
- stdout = job.GetenvBool("stdout")
|
|
| 27 |
- stderr = job.GetenvBool("stderr")
|
|
| 28 |
- tail = job.Getenv("tail")
|
|
| 29 |
- follow = job.GetenvBool("follow")
|
|
| 30 |
- times = job.GetenvBool("timestamps")
|
|
| 31 | 28 |
lines = -1 |
| 32 | 29 |
format string |
| 33 | 30 |
) |
| 34 |
- if !(stdout || stderr) {
|
|
| 31 |
+ if !(config.UseStdout || config.UseStderr) {
|
|
| 35 | 32 |
return fmt.Errorf("You must choose at least one stream")
|
| 36 | 33 |
} |
| 37 |
- if times {
|
|
| 34 |
+ if config.Timestamps {
|
|
| 38 | 35 |
format = timeutils.RFC3339NanoFixed |
| 39 | 36 |
} |
| 40 |
- if tail == "" {
|
|
| 41 |
- tail = "all" |
|
| 37 |
+ if config.Tail == "" {
|
|
| 38 |
+ config.Tail = "all" |
|
| 42 | 39 |
} |
| 40 |
+ |
|
| 43 | 41 |
container, err := daemon.Get(name) |
| 44 | 42 |
if err != nil {
|
| 45 | 43 |
return err |
| 46 | 44 |
} |
| 45 |
+ |
|
| 46 |
+ var ( |
|
| 47 |
+ outStream = config.OutStream |
|
| 48 |
+ errStream io.Writer |
|
| 49 |
+ ) |
|
| 50 |
+ if !container.Config.Tty {
|
|
| 51 |
+ errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) |
|
| 52 |
+ outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) |
|
| 53 |
+ } else {
|
|
| 54 |
+ errStream = outStream |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 47 | 57 |
if container.LogDriverType() != "json-file" {
|
| 48 | 58 |
return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver")
|
| 49 | 59 |
} |
| ... | ... |
@@ -51,30 +61,30 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) error {
|
| 51 | 51 |
if err != nil && os.IsNotExist(err) {
|
| 52 | 52 |
// Legacy logs |
| 53 | 53 |
logrus.Debugf("Old logs format")
|
| 54 |
- if stdout {
|
|
| 54 |
+ if config.UseStdout {
|
|
| 55 | 55 |
cLog, err := container.ReadLog("stdout")
|
| 56 | 56 |
if err != nil {
|
| 57 | 57 |
logrus.Errorf("Error reading logs (stdout): %s", err)
|
| 58 |
- } else if _, err := io.Copy(job.Stdout, cLog); err != nil {
|
|
| 58 |
+ } else if _, err := io.Copy(outStream, cLog); err != nil {
|
|
| 59 | 59 |
logrus.Errorf("Error streaming logs (stdout): %s", err)
|
| 60 | 60 |
} |
| 61 | 61 |
} |
| 62 |
- if stderr {
|
|
| 62 |
+ if config.UseStderr {
|
|
| 63 | 63 |
cLog, err := container.ReadLog("stderr")
|
| 64 | 64 |
if err != nil {
|
| 65 | 65 |
logrus.Errorf("Error reading logs (stderr): %s", err)
|
| 66 |
- } else if _, err := io.Copy(job.Stderr, cLog); err != nil {
|
|
| 66 |
+ } else if _, err := io.Copy(errStream, cLog); err != nil {
|
|
| 67 | 67 |
logrus.Errorf("Error streaming logs (stderr): %s", err)
|
| 68 | 68 |
} |
| 69 | 69 |
} |
| 70 | 70 |
} else if err != nil {
|
| 71 | 71 |
logrus.Errorf("Error reading logs (json): %s", err)
|
| 72 | 72 |
} else {
|
| 73 |
- if tail != "all" {
|
|
| 73 |
+ if config.Tail != "all" {
|
|
| 74 | 74 |
var err error |
| 75 |
- lines, err = strconv.Atoi(tail) |
|
| 75 |
+ lines, err = strconv.Atoi(config.Tail) |
|
| 76 | 76 |
if err != nil {
|
| 77 |
- logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", tail, err)
|
|
| 77 |
+ logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", config.Tail, err)
|
|
| 78 | 78 |
lines = -1 |
| 79 | 79 |
} |
| 80 | 80 |
} |
| ... | ... |
@@ -101,39 +111,39 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) error {
|
| 101 | 101 |
break |
| 102 | 102 |
} |
| 103 | 103 |
logLine := l.Log |
| 104 |
- if times {
|
|
| 104 |
+ if config.Timestamps {
|
|
| 105 | 105 |
// format can be "" or time format, so here can't be error |
| 106 | 106 |
logLine, _ = l.Format(format) |
| 107 | 107 |
} |
| 108 |
- if l.Stream == "stdout" && stdout {
|
|
| 109 |
- io.WriteString(job.Stdout, logLine) |
|
| 108 |
+ if l.Stream == "stdout" && config.UseStdout {
|
|
| 109 |
+ io.WriteString(outStream, logLine) |
|
| 110 | 110 |
} |
| 111 |
- if l.Stream == "stderr" && stderr {
|
|
| 112 |
- io.WriteString(job.Stderr, logLine) |
|
| 111 |
+ if l.Stream == "stderr" && config.UseStderr {
|
|
| 112 |
+ io.WriteString(errStream, logLine) |
|
| 113 | 113 |
} |
| 114 | 114 |
l.Reset() |
| 115 | 115 |
} |
| 116 | 116 |
} |
| 117 | 117 |
} |
| 118 |
- if follow && container.IsRunning() {
|
|
| 118 |
+ if config.Follow && container.IsRunning() {
|
|
| 119 | 119 |
errors := make(chan error, 2) |
| 120 | 120 |
wg := sync.WaitGroup{}
|
| 121 | 121 |
|
| 122 |
- if stdout {
|
|
| 122 |
+ if config.UseStdout {
|
|
| 123 | 123 |
wg.Add(1) |
| 124 | 124 |
stdoutPipe := container.StdoutLogPipe() |
| 125 | 125 |
defer stdoutPipe.Close() |
| 126 | 126 |
go func() {
|
| 127 |
- errors <- jsonlog.WriteLog(stdoutPipe, job.Stdout, format) |
|
| 127 |
+ errors <- jsonlog.WriteLog(stdoutPipe, outStream, format) |
|
| 128 | 128 |
wg.Done() |
| 129 | 129 |
}() |
| 130 | 130 |
} |
| 131 |
- if stderr {
|
|
| 131 |
+ if config.UseStderr {
|
|
| 132 | 132 |
wg.Add(1) |
| 133 | 133 |
stderrPipe := container.StderrLogPipe() |
| 134 | 134 |
defer stderrPipe.Close() |
| 135 | 135 |
go func() {
|
| 136 |
- errors <- jsonlog.WriteLog(stderrPipe, job.Stderr, format) |
|
| 136 |
+ errors <- jsonlog.WriteLog(stderrPipe, errStream, format) |
|
| 137 | 137 |
wg.Done() |
| 138 | 138 |
}() |
| 139 | 139 |
} |
| ... | ... |
@@ -3,14 +3,15 @@ package main |
| 3 | 3 |
import ( |
| 4 | 4 |
"bytes" |
| 5 | 5 |
"encoding/json" |
| 6 |
- "github.com/docker/docker/api/types" |
|
| 7 |
- "github.com/docker/docker/pkg/stringid" |
|
| 8 |
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" |
|
| 9 | 6 |
"io" |
| 10 | 7 |
"os/exec" |
| 11 | 8 |
"strings" |
| 12 | 9 |
"testing" |
| 13 | 10 |
"time" |
| 11 |
+ |
|
| 12 |
+ "github.com/docker/docker/api/types" |
|
| 13 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 14 |
+ "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" |
|
| 14 | 15 |
) |
| 15 | 16 |
|
| 16 | 17 |
func TestContainerApiGetAll(t *testing.T) {
|
| ... | ... |
@@ -28,7 +29,7 @@ func TestContainerApiGetAll(t *testing.T) {
|
| 28 | 28 |
t.Fatalf("Error on container creation: %v, output: %q", err, out)
|
| 29 | 29 |
} |
| 30 | 30 |
|
| 31 |
- body, err := sockRequest("GET", "/containers/json?all=1", nil)
|
|
| 31 |
+ _, body, err := sockRequest("GET", "/containers/json?all=1", nil)
|
|
| 32 | 32 |
if err != nil {
|
| 33 | 33 |
t.Fatalf("GET all containers sockRequest failed: %v", err)
|
| 34 | 34 |
} |
| ... | ... |
@@ -61,7 +62,7 @@ func TestContainerApiGetExport(t *testing.T) {
|
| 61 | 61 |
t.Fatalf("Error on container creation: %v, output: %q", err, out)
|
| 62 | 62 |
} |
| 63 | 63 |
|
| 64 |
- body, err := sockRequest("GET", "/containers/"+name+"/export", nil)
|
|
| 64 |
+ _, body, err := sockRequest("GET", "/containers/"+name+"/export", nil)
|
|
| 65 | 65 |
if err != nil {
|
| 66 | 66 |
t.Fatalf("GET containers/export sockRequest failed: %v", err)
|
| 67 | 67 |
} |
| ... | ... |
@@ -98,7 +99,7 @@ func TestContainerApiGetChanges(t *testing.T) {
|
| 98 | 98 |
t.Fatalf("Error on container creation: %v, output: %q", err, out)
|
| 99 | 99 |
} |
| 100 | 100 |
|
| 101 |
- body, err := sockRequest("GET", "/containers/"+name+"/changes", nil)
|
|
| 101 |
+ _, body, err := sockRequest("GET", "/containers/"+name+"/changes", nil)
|
|
| 102 | 102 |
if err != nil {
|
| 103 | 103 |
t.Fatalf("GET containers/changes sockRequest failed: %v", err)
|
| 104 | 104 |
} |
| ... | ... |
@@ -133,7 +134,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) {
|
| 133 | 133 |
"Volumes": map[string]struct{}{"/tmp": {}},
|
| 134 | 134 |
} |
| 135 | 135 |
|
| 136 |
- if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
|
|
| 136 |
+ if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
|
|
| 137 | 137 |
t.Fatal(err) |
| 138 | 138 |
} |
| 139 | 139 |
|
| ... | ... |
@@ -141,7 +142,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) {
|
| 141 | 141 |
config = map[string]interface{}{
|
| 142 | 142 |
"Binds": []string{bindPath + ":/tmp"},
|
| 143 | 143 |
} |
| 144 |
- if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
|
|
| 144 |
+ if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
|
|
| 145 | 145 |
t.Fatal(err) |
| 146 | 146 |
} |
| 147 | 147 |
|
| ... | ... |
@@ -166,7 +167,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) {
|
| 166 | 166 |
"Volumes": map[string]struct{}{"/tmp": {}},
|
| 167 | 167 |
} |
| 168 | 168 |
|
| 169 |
- if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
|
|
| 169 |
+ if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
|
|
| 170 | 170 |
t.Fatal(err) |
| 171 | 171 |
} |
| 172 | 172 |
|
| ... | ... |
@@ -176,7 +177,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) {
|
| 176 | 176 |
config = map[string]interface{}{
|
| 177 | 177 |
"Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"},
|
| 178 | 178 |
} |
| 179 |
- if body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil {
|
|
| 179 |
+ if _, body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil {
|
|
| 180 | 180 |
t.Fatal("expected container start to fail when duplicate volume binds to same container path")
|
| 181 | 181 |
} else {
|
| 182 | 182 |
if !strings.Contains(string(body), "Duplicate volume") {
|
| ... | ... |
@@ -201,14 +202,14 @@ func TestContainerApiStartVolumesFrom(t *testing.T) {
|
| 201 | 201 |
"Volumes": map[string]struct{}{volPath: {}},
|
| 202 | 202 |
} |
| 203 | 203 |
|
| 204 |
- if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
|
|
| 204 |
+ if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
|
|
| 205 | 205 |
t.Fatal(err) |
| 206 | 206 |
} |
| 207 | 207 |
|
| 208 | 208 |
config = map[string]interface{}{
|
| 209 | 209 |
"VolumesFrom": []string{volName},
|
| 210 | 210 |
} |
| 211 |
- if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
|
|
| 211 |
+ if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
|
|
| 212 | 212 |
t.Fatal(err) |
| 213 | 213 |
} |
| 214 | 214 |
|
| ... | ... |
@@ -245,7 +246,7 @@ func TestVolumesFromHasPriority(t *testing.T) {
|
| 245 | 245 |
"Volumes": map[string]struct{}{volPath: {}},
|
| 246 | 246 |
} |
| 247 | 247 |
|
| 248 |
- if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
|
|
| 248 |
+ if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
|
|
| 249 | 249 |
t.Fatal(err) |
| 250 | 250 |
} |
| 251 | 251 |
|
| ... | ... |
@@ -254,7 +255,7 @@ func TestVolumesFromHasPriority(t *testing.T) {
|
| 254 | 254 |
"VolumesFrom": []string{volName},
|
| 255 | 255 |
"Binds": []string{bindPath + ":/tmp"},
|
| 256 | 256 |
} |
| 257 |
- if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
|
|
| 257 |
+ if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
|
|
| 258 | 258 |
t.Fatal(err) |
| 259 | 259 |
} |
| 260 | 260 |
|
| ... | ... |
@@ -290,7 +291,7 @@ func TestGetContainerStats(t *testing.T) {
|
| 290 | 290 |
} |
| 291 | 291 |
bc := make(chan b, 1) |
| 292 | 292 |
go func() {
|
| 293 |
- body, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
|
|
| 293 |
+ _, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
|
|
| 294 | 294 |
bc <- b{body, err}
|
| 295 | 295 |
}() |
| 296 | 296 |
|
| ... | ... |
@@ -334,7 +335,7 @@ func TestGetStoppedContainerStats(t *testing.T) {
|
| 334 | 334 |
go func() {
|
| 335 | 335 |
// We'll never get return for GET stats from sockRequest as of now, |
| 336 | 336 |
// just send request and see if panic or error would happen on daemon side. |
| 337 |
- _, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
|
|
| 337 |
+ _, _, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
|
|
| 338 | 338 |
if err != nil {
|
| 339 | 339 |
t.Fatal(err) |
| 340 | 340 |
} |
| ... | ... |
@@ -367,7 +368,7 @@ func TestBuildApiDockerfilePath(t *testing.T) {
|
| 367 | 367 |
t.Fatalf("failed to close tar archive: %v", err)
|
| 368 | 368 |
} |
| 369 | 369 |
|
| 370 |
- out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar")
|
|
| 370 |
+ _, out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar")
|
|
| 371 | 371 |
if err == nil {
|
| 372 | 372 |
t.Fatalf("Build was supposed to fail: %s", out)
|
| 373 | 373 |
} |
| ... | ... |
@@ -391,7 +392,7 @@ RUN find /tmp/`, |
| 391 | 391 |
} |
| 392 | 392 |
defer server.Close() |
| 393 | 393 |
|
| 394 |
- buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json")
|
|
| 394 |
+ _, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json")
|
|
| 395 | 395 |
if err != nil {
|
| 396 | 396 |
t.Fatalf("Build failed: %s", err)
|
| 397 | 397 |
} |
| ... | ... |
@@ -417,7 +418,7 @@ RUN echo from dockerfile`, |
| 417 | 417 |
} |
| 418 | 418 |
defer git.Close() |
| 419 | 419 |
|
| 420 |
- buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
|
|
| 420 |
+ _, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
|
|
| 421 | 421 |
if err != nil {
|
| 422 | 422 |
t.Fatalf("Build failed: %s\n%q", err, buf)
|
| 423 | 423 |
} |
| ... | ... |
@@ -443,7 +444,7 @@ RUN echo from Dockerfile`, |
| 443 | 443 |
defer git.Close() |
| 444 | 444 |
|
| 445 | 445 |
// Make sure it tries to 'dockerfile' query param value |
| 446 |
- buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json")
|
|
| 446 |
+ _, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json")
|
|
| 447 | 447 |
if err != nil {
|
| 448 | 448 |
t.Fatalf("Build failed: %s\n%q", err, buf)
|
| 449 | 449 |
} |
| ... | ... |
@@ -470,7 +471,7 @@ RUN echo from dockerfile`, |
| 470 | 470 |
defer git.Close() |
| 471 | 471 |
|
| 472 | 472 |
// Make sure it tries to 'dockerfile' query param value |
| 473 |
- buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
|
|
| 473 |
+ _, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
|
|
| 474 | 474 |
if err != nil {
|
| 475 | 475 |
t.Fatalf("Build failed: %s", err)
|
| 476 | 476 |
} |
| ... | ... |
@@ -501,7 +502,7 @@ func TestBuildApiDockerfileSymlink(t *testing.T) {
|
| 501 | 501 |
t.Fatalf("failed to close tar archive: %v", err)
|
| 502 | 502 |
} |
| 503 | 503 |
|
| 504 |
- out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar")
|
|
| 504 |
+ _, out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar")
|
|
| 505 | 505 |
if err == nil {
|
| 506 | 506 |
t.Fatalf("Build was supposed to fail: %s", out)
|
| 507 | 507 |
} |
| ... | ... |
@@ -537,7 +538,7 @@ func TestPostContainerBindNormalVolume(t *testing.T) {
|
| 537 | 537 |
} |
| 538 | 538 |
|
| 539 | 539 |
bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}}
|
| 540 |
- _, err = sockRequest("POST", "/containers/two/start", bindSpec)
|
|
| 540 |
+ _, _, err = sockRequest("POST", "/containers/two/start", bindSpec)
|
|
| 541 | 541 |
if err != nil && !strings.Contains(err.Error(), "204 No Content") {
|
| 542 | 542 |
t.Fatal(err) |
| 543 | 543 |
} |
| ... | ... |
@@ -565,7 +566,7 @@ func TestContainerApiPause(t *testing.T) {
|
| 565 | 565 |
} |
| 566 | 566 |
ContainerID := strings.TrimSpace(out) |
| 567 | 567 |
|
| 568 |
- if _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") {
|
|
| 568 |
+ if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") {
|
|
| 569 | 569 |
t.Fatalf("POST a container pause: sockRequest failed: %v", err)
|
| 570 | 570 |
} |
| 571 | 571 |
|
| ... | ... |
@@ -579,7 +580,7 @@ func TestContainerApiPause(t *testing.T) {
|
| 579 | 579 |
t.Fatalf("there should be one paused container and not %d", len(pausedContainers))
|
| 580 | 580 |
} |
| 581 | 581 |
|
| 582 |
- if _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") {
|
|
| 582 |
+ if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") {
|
|
| 583 | 583 |
t.Fatalf("POST a container pause: sockRequest failed: %v", err)
|
| 584 | 584 |
} |
| 585 | 585 |
|
| ... | ... |
@@ -18,7 +18,7 @@ func TestExecApiCreateNoCmd(t *testing.T) {
|
| 18 | 18 |
t.Fatal(out, err) |
| 19 | 19 |
} |
| 20 | 20 |
|
| 21 |
- body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil})
|
|
| 21 |
+ _, body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil})
|
|
| 22 | 22 |
if err == nil || !bytes.Contains(body, []byte("No exec command specified")) {
|
| 23 | 23 |
t.Fatalf("Expected error when creating exec command with no Cmd specified: %q", err)
|
| 24 | 24 |
} |
| ... | ... |
@@ -27,7 +27,7 @@ func TestInspectApiContainerResponse(t *testing.T) {
|
| 27 | 27 |
if testVersion != "latest" {
|
| 28 | 28 |
endpoint = "/" + testVersion + endpoint |
| 29 | 29 |
} |
| 30 |
- body, err := sockRequest("GET", endpoint, nil)
|
|
| 30 |
+ _, body, err := sockRequest("GET", endpoint, nil)
|
|
| 31 | 31 |
if err != nil {
|
| 32 | 32 |
t.Fatalf("sockRequest failed for %s version: %v", testVersion, err)
|
| 33 | 33 |
} |
| 34 | 34 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,53 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "net/http" |
|
| 6 |
+ "os/exec" |
|
| 7 |
+ "testing" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func TestLogsApiWithStdout(t *testing.T) {
|
|
| 11 |
+ defer deleteAllContainers() |
|
| 12 |
+ name := "logs_test" |
|
| 13 |
+ |
|
| 14 |
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "bin/sh", "-c", "sleep 10 && echo "+name) |
|
| 15 |
+ if out, _, err := runCommandWithOutput(runCmd); err != nil {
|
|
| 16 |
+ t.Fatal(out, err) |
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 19 |
+ statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", name), nil)
|
|
| 20 |
+ |
|
| 21 |
+ if err != nil || statusCode != http.StatusOK {
|
|
| 22 |
+ t.Fatalf("Expected %d from logs request, got %d", http.StatusOK, statusCode)
|
|
| 23 |
+ } |
|
| 24 |
+ |
|
| 25 |
+ if !bytes.Contains(body, []byte(name)) {
|
|
| 26 |
+ t.Fatalf("Expected %s, got %s", name, string(body[:]))
|
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 29 |
+ logDone("logs API - with stdout ok")
|
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+func TestLogsApiNoStdoutNorStderr(t *testing.T) {
|
|
| 33 |
+ defer deleteAllContainers() |
|
| 34 |
+ name := "logs_test" |
|
| 35 |
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh") |
|
| 36 |
+ if out, _, err := runCommandWithOutput(runCmd); err != nil {
|
|
| 37 |
+ t.Fatal(out, err) |
|
| 38 |
+ } |
|
| 39 |
+ |
|
| 40 |
+ statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs", name), nil)
|
|
| 41 |
+ |
|
| 42 |
+ if err == nil || statusCode != http.StatusBadRequest {
|
|
| 43 |
+ t.Fatalf("Expected %d from logs request, got %d", http.StatusBadRequest, statusCode)
|
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ expected := "Bad parameters: you must choose at least one stream" |
|
| 47 |
+ if !bytes.Contains(body, []byte(expected)) {
|
|
| 48 |
+ t.Fatalf("Expected %s, got %s", expected, string(body[:]))
|
|
| 49 |
+ } |
|
| 50 |
+ |
|
| 51 |
+ logDone("logs API - returns error when no stdout nor stderr specified")
|
|
| 52 |
+} |
| ... | ... |
@@ -16,7 +16,7 @@ func TestResizeApiResponse(t *testing.T) {
|
| 16 | 16 |
cleanedContainerID := strings.TrimSpace(out) |
| 17 | 17 |
|
| 18 | 18 |
endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" |
| 19 |
- _, err = sockRequest("POST", endpoint, nil)
|
|
| 19 |
+ _, _, err = sockRequest("POST", endpoint, nil)
|
|
| 20 | 20 |
if err != nil {
|
| 21 | 21 |
t.Fatalf("resize Request failed %v", err)
|
| 22 | 22 |
} |
| ... | ... |
@@ -41,7 +41,7 @@ func TestResizeApiResponseWhenContainerNotStarted(t *testing.T) {
|
| 41 | 41 |
} |
| 42 | 42 |
|
| 43 | 43 |
endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" |
| 44 |
- body, err := sockRequest("POST", endpoint, nil)
|
|
| 44 |
+ _, body, err := sockRequest("POST", endpoint, nil)
|
|
| 45 | 45 |
if err == nil {
|
| 46 | 46 |
t.Fatalf("resize should fail when container is not started")
|
| 47 | 47 |
} |
| ... | ... |
@@ -64,7 +64,7 @@ func TestRmRunningContainerCheckError409(t *testing.T) {
|
| 64 | 64 |
createRunningContainer(t, "foo") |
| 65 | 65 |
|
| 66 | 66 |
endpoint := "/containers/foo" |
| 67 |
- _, err := sockRequest("DELETE", endpoint, nil)
|
|
| 67 |
+ _, _, err := sockRequest("DELETE", endpoint, nil)
|
|
| 68 | 68 |
|
| 69 | 69 |
if err == nil {
|
| 70 | 70 |
t.Fatalf("Expected error, can't rm a running container")
|
| ... | ... |
@@ -298,19 +298,19 @@ func sockConn(timeout time.Duration) (net.Conn, error) {
|
| 298 | 298 |
} |
| 299 | 299 |
} |
| 300 | 300 |
|
| 301 |
-func sockRequest(method, endpoint string, data interface{}) ([]byte, error) {
|
|
| 301 |
+func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) {
|
|
| 302 | 302 |
jsonData := bytes.NewBuffer(nil) |
| 303 | 303 |
if err := json.NewEncoder(jsonData).Encode(data); err != nil {
|
| 304 |
- return nil, err |
|
| 304 |
+ return -1, nil, err |
|
| 305 | 305 |
} |
| 306 | 306 |
|
| 307 | 307 |
return sockRequestRaw(method, endpoint, jsonData, "application/json") |
| 308 | 308 |
} |
| 309 | 309 |
|
| 310 |
-func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, error) {
|
|
| 310 |
+func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, []byte, error) {
|
|
| 311 | 311 |
c, err := sockConn(time.Duration(10 * time.Second)) |
| 312 | 312 |
if err != nil {
|
| 313 |
- return nil, fmt.Errorf("could not dial docker daemon: %v", err)
|
|
| 313 |
+ return -1, nil, fmt.Errorf("could not dial docker daemon: %v", err)
|
|
| 314 | 314 |
} |
| 315 | 315 |
|
| 316 | 316 |
client := httputil.NewClientConn(c, nil) |
| ... | ... |
@@ -318,7 +318,7 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, |
| 318 | 318 |
|
| 319 | 319 |
req, err := http.NewRequest(method, endpoint, data) |
| 320 | 320 |
if err != nil {
|
| 321 |
- return nil, fmt.Errorf("could not create new request: %v", err)
|
|
| 321 |
+ return -1, nil, fmt.Errorf("could not create new request: %v", err)
|
|
| 322 | 322 |
} |
| 323 | 323 |
|
| 324 | 324 |
if ct == "" {
|
| ... | ... |
@@ -328,15 +328,17 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, |
| 328 | 328 |
|
| 329 | 329 |
resp, err := client.Do(req) |
| 330 | 330 |
if err != nil {
|
| 331 |
- return nil, fmt.Errorf("could not perform request: %v", err)
|
|
| 331 |
+ return -1, nil, fmt.Errorf("could not perform request: %v", err)
|
|
| 332 | 332 |
} |
| 333 | 333 |
defer resp.Body.Close() |
| 334 | 334 |
if resp.StatusCode != http.StatusOK {
|
| 335 | 335 |
body, _ := ioutil.ReadAll(resp.Body) |
| 336 |
- return body, fmt.Errorf("received status != 200 OK: %s", resp.Status)
|
|
| 336 |
+ return resp.StatusCode, body, fmt.Errorf("received status != 200 OK: %s", resp.Status)
|
|
| 337 | 337 |
} |
| 338 | 338 |
|
| 339 |
- return ioutil.ReadAll(resp.Body) |
|
| 339 |
+ b, err := ioutil.ReadAll(resp.Body) |
|
| 340 |
+ |
|
| 341 |
+ return resp.StatusCode, b, err |
|
| 340 | 342 |
} |
| 341 | 343 |
|
| 342 | 344 |
func deleteContainer(container string) error {
|
| ... | ... |
@@ -1041,7 +1043,7 @@ func daemonTime(t *testing.T) time.Time {
|
| 1041 | 1041 |
return time.Now() |
| 1042 | 1042 |
} |
| 1043 | 1043 |
|
| 1044 |
- body, err := sockRequest("GET", "/info", nil)
|
|
| 1044 |
+ _, body, err := sockRequest("GET", "/info", nil)
|
|
| 1045 | 1045 |
if err != nil {
|
| 1046 | 1046 |
t.Fatalf("daemonTime: failed to get /info: %v", err)
|
| 1047 | 1047 |
} |
| ... | ... |
@@ -57,7 +57,7 @@ var ( |
| 57 | 57 |
func() bool {
|
| 58 | 58 |
if daemonExecDriver == "" {
|
| 59 | 59 |
// get daemon info |
| 60 |
- body, err := sockRequest("GET", "/info", nil)
|
|
| 60 |
+ _, body, err := sockRequest("GET", "/info", nil)
|
|
| 61 | 61 |
if err != nil {
|
| 62 | 62 |
log.Fatalf("sockRequest failed for /info: %v", err)
|
| 63 | 63 |
} |