Browse code

Validate that one of streams choosen in logs on api side

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)

LK4D4 authored on 2014/06/19 04:11:04
Showing 2 changed files
... ...
@@ -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&timestamps=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)