Fixes #6506
There is the bug, that very hard to fix: When we return job.Errorf in
"logs" job it writes to job.Stderr, to which connected ResponseWriter and on
this write w.WriteHeader(http.StatusOK) is called. So, we get 200 on error
from "logs" job.
Docker-DCO-1.1-Signed-off-by: Alexandr Morozov <lk4d4math@gmail.com> (github: LK4D4)
| ... | ... |
@@ -370,13 +370,23 @@ func getContainersLogs(eng *engine.Engine, version version.Version, w http.Respo |
| 370 | 370 |
} |
| 371 | 371 |
|
| 372 | 372 |
var ( |
| 373 |
- job = eng.Job("container_inspect", vars["name"])
|
|
| 374 |
- c, err = job.Stdout.AddEnv() |
|
| 373 |
+ inspectJob = eng.Job("container_inspect", vars["name"])
|
|
| 374 |
+ logsJob = eng.Job("logs", vars["name"])
|
|
| 375 |
+ c, err = inspectJob.Stdout.AddEnv() |
|
| 375 | 376 |
) |
| 376 | 377 |
if err != nil {
|
| 377 | 378 |
return err |
| 378 | 379 |
} |
| 379 |
- if err = job.Run(); err != nil {
|
|
| 380 |
+ logsJob.Setenv("follow", r.Form.Get("follow"))
|
|
| 381 |
+ logsJob.Setenv("stdout", r.Form.Get("stdout"))
|
|
| 382 |
+ logsJob.Setenv("stderr", r.Form.Get("stderr"))
|
|
| 383 |
+ logsJob.Setenv("timestamps", r.Form.Get("timestamps"))
|
|
| 384 |
+ // Validate args here, because we can't return not StatusOK after job.Run() call |
|
| 385 |
+ stdout, stderr := logsJob.GetenvBool("stdout"), logsJob.GetenvBool("stderr")
|
|
| 386 |
+ if !(stdout || stderr) {
|
|
| 387 |
+ return fmt.Errorf("Bad parameters: you must choose at least one stream")
|
|
| 388 |
+ } |
|
| 389 |
+ if err = inspectJob.Run(); err != nil {
|
|
| 380 | 390 |
return err |
| 381 | 391 |
} |
| 382 | 392 |
|
| ... | ... |
@@ -390,14 +400,9 @@ func getContainersLogs(eng *engine.Engine, version version.Version, w http.Respo |
| 390 | 390 |
errStream = outStream |
| 391 | 391 |
} |
| 392 | 392 |
|
| 393 |
- job = eng.Job("logs", vars["name"])
|
|
| 394 |
- job.Setenv("follow", r.Form.Get("follow"))
|
|
| 395 |
- job.Setenv("stdout", r.Form.Get("stdout"))
|
|
| 396 |
- job.Setenv("stderr", r.Form.Get("stderr"))
|
|
| 397 |
- job.Setenv("timestamps", r.Form.Get("timestamps"))
|
|
| 398 |
- job.Stdout.Add(outStream) |
|
| 399 |
- job.Stderr.Set(errStream) |
|
| 400 |
- if err := job.Run(); err != nil {
|
|
| 393 |
+ logsJob.Stdout.Add(outStream) |
|
| 394 |
+ logsJob.Stderr.Set(errStream) |
|
| 395 |
+ if err := logsJob.Run(); err != nil {
|
|
| 401 | 396 |
fmt.Fprintf(outStream, "Error running logs job: %s\n", err) |
| 402 | 397 |
} |
| 403 | 398 |
return nil |
| ... | ... |
@@ -4,12 +4,14 @@ import ( |
| 4 | 4 |
"bytes" |
| 5 | 5 |
"encoding/json" |
| 6 | 6 |
"fmt" |
| 7 |
- "github.com/dotcloud/docker/api" |
|
| 8 |
- "github.com/dotcloud/docker/engine" |
|
| 9 | 7 |
"io" |
| 10 | 8 |
"net/http" |
| 11 | 9 |
"net/http/httptest" |
| 10 |
+ "strings" |
|
| 12 | 11 |
"testing" |
| 12 |
+ |
|
| 13 |
+ "github.com/dotcloud/docker/api" |
|
| 14 |
+ "github.com/dotcloud/docker/engine" |
|
| 13 | 15 |
) |
| 14 | 16 |
|
| 15 | 17 |
func TestGetBoolParam(t *testing.T) {
|
| ... | ... |
@@ -151,6 +153,99 @@ func TestGetContainersByName(t *testing.T) {
|
| 151 | 151 |
} |
| 152 | 152 |
} |
| 153 | 153 |
|
| 154 |
+func TestLogs(t *testing.T) {
|
|
| 155 |
+ eng := engine.New() |
|
| 156 |
+ var inspect bool |
|
| 157 |
+ var logs bool |
|
| 158 |
+ eng.Register("container_inspect", func(job *engine.Job) engine.Status {
|
|
| 159 |
+ inspect = true |
|
| 160 |
+ if len(job.Args) == 0 {
|
|
| 161 |
+ t.Fatal("Job arguments is empty")
|
|
| 162 |
+ } |
|
| 163 |
+ if job.Args[0] != "test" {
|
|
| 164 |
+ t.Fatalf("Container name %s, must be test", job.Args[0])
|
|
| 165 |
+ } |
|
| 166 |
+ return engine.StatusOK |
|
| 167 |
+ }) |
|
| 168 |
+ expected := "logs" |
|
| 169 |
+ eng.Register("logs", func(job *engine.Job) engine.Status {
|
|
| 170 |
+ logs = true |
|
| 171 |
+ if len(job.Args) == 0 {
|
|
| 172 |
+ t.Fatal("Job arguments is empty")
|
|
| 173 |
+ } |
|
| 174 |
+ if job.Args[0] != "test" {
|
|
| 175 |
+ t.Fatalf("Container name %s, must be test", job.Args[0])
|
|
| 176 |
+ } |
|
| 177 |
+ follow := job.Getenv("follow")
|
|
| 178 |
+ if follow != "1" {
|
|
| 179 |
+ t.Fatalf("follow: %s, must be 1", follow)
|
|
| 180 |
+ } |
|
| 181 |
+ stdout := job.Getenv("stdout")
|
|
| 182 |
+ if stdout != "1" {
|
|
| 183 |
+ t.Fatalf("stdout %s, must be 1", stdout)
|
|
| 184 |
+ } |
|
| 185 |
+ stderr := job.Getenv("stderr")
|
|
| 186 |
+ if stderr != "" {
|
|
| 187 |
+ t.Fatalf("stderr %s, must be empty", stderr)
|
|
| 188 |
+ } |
|
| 189 |
+ timestamps := job.Getenv("timestamps")
|
|
| 190 |
+ if timestamps != "1" {
|
|
| 191 |
+ t.Fatalf("timestamps %s, must be 1", timestamps)
|
|
| 192 |
+ } |
|
| 193 |
+ job.Stdout.Write([]byte(expected)) |
|
| 194 |
+ return engine.StatusOK |
|
| 195 |
+ }) |
|
| 196 |
+ r := serveRequest("GET", "/containers/test/logs?follow=1&stdout=1×tamps=1", nil, eng, t)
|
|
| 197 |
+ if r.Code != http.StatusOK {
|
|
| 198 |
+ t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK)
|
|
| 199 |
+ } |
|
| 200 |
+ if !inspect {
|
|
| 201 |
+ t.Fatal("container_inspect job was not called")
|
|
| 202 |
+ } |
|
| 203 |
+ if !logs {
|
|
| 204 |
+ t.Fatal("logs job was not called")
|
|
| 205 |
+ } |
|
| 206 |
+ res := r.Body.String() |
|
| 207 |
+ if res != expected {
|
|
| 208 |
+ t.Fatalf("Output %s, expected %s", res, expected)
|
|
| 209 |
+ } |
|
| 210 |
+} |
|
| 211 |
+ |
|
| 212 |
+func TestLogsNoStreams(t *testing.T) {
|
|
| 213 |
+ eng := engine.New() |
|
| 214 |
+ var inspect bool |
|
| 215 |
+ var logs bool |
|
| 216 |
+ eng.Register("container_inspect", func(job *engine.Job) engine.Status {
|
|
| 217 |
+ inspect = true |
|
| 218 |
+ if len(job.Args) == 0 {
|
|
| 219 |
+ t.Fatal("Job arguments is empty")
|
|
| 220 |
+ } |
|
| 221 |
+ if job.Args[0] != "test" {
|
|
| 222 |
+ t.Fatalf("Container name %s, must be test", job.Args[0])
|
|
| 223 |
+ } |
|
| 224 |
+ return engine.StatusOK |
|
| 225 |
+ }) |
|
| 226 |
+ eng.Register("logs", func(job *engine.Job) engine.Status {
|
|
| 227 |
+ logs = true |
|
| 228 |
+ return engine.StatusOK |
|
| 229 |
+ }) |
|
| 230 |
+ r := serveRequest("GET", "/containers/test/logs", nil, eng, t)
|
|
| 231 |
+ if r.Code != http.StatusBadRequest {
|
|
| 232 |
+ t.Fatalf("Got status %d, expected %d", r.Code, http.StatusBadRequest)
|
|
| 233 |
+ } |
|
| 234 |
+ if inspect {
|
|
| 235 |
+ t.Fatal("container_inspect job was called, but it shouldn't")
|
|
| 236 |
+ } |
|
| 237 |
+ if logs {
|
|
| 238 |
+ t.Fatal("logs job was called, but it shouldn't")
|
|
| 239 |
+ } |
|
| 240 |
+ res := strings.TrimSpace(r.Body.String()) |
|
| 241 |
+ expected := "Bad parameters: you must choose at least one stream" |
|
| 242 |
+ if !strings.Contains(res, expected) {
|
|
| 243 |
+ t.Fatalf("Output %s, expected %s in it", res, expected)
|
|
| 244 |
+ } |
|
| 245 |
+} |
|
| 246 |
+ |
|
| 154 | 247 |
func serveRequest(method, target string, body io.Reader, eng *engine.Engine, t *testing.T) *httptest.ResponseRecorder {
|
| 155 | 248 |
r := httptest.NewRecorder() |
| 156 | 249 |
req, err := http.NewRequest(method, target, body) |