Signed-off-by: Ben Firshman <ben@firshman.co.uk>
| ... | ... |
@@ -5,6 +5,9 @@ import ( |
| 5 | 5 |
"strings" |
| 6 | 6 |
|
| 7 | 7 |
"github.com/Sirupsen/logrus" |
| 8 |
+ "github.com/docker/engine-api/types" |
|
| 9 |
+ "github.com/docker/engine-api/types/versions" |
|
| 10 |
+ "github.com/gorilla/mux" |
|
| 8 | 11 |
) |
| 9 | 12 |
|
| 10 | 13 |
// httpStatusError is an interface |
| ... | ... |
@@ -70,13 +73,19 @@ func GetHTTPErrorStatusCode(err error) int {
|
| 70 | 70 |
return statusCode |
| 71 | 71 |
} |
| 72 | 72 |
|
| 73 |
-// WriteError decodes a specific docker error and sends it in the response. |
|
| 74 |
-func WriteError(w http.ResponseWriter, err error) {
|
|
| 75 |
- if err == nil || w == nil {
|
|
| 76 |
- logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
|
|
| 77 |
- return |
|
| 73 |
+// MakeErrorHandler makes an HTTP handler that decodes a Docker error and |
|
| 74 |
+// returns it in the response. |
|
| 75 |
+func MakeErrorHandler(err error) http.HandlerFunc {
|
|
| 76 |
+ return func(w http.ResponseWriter, r *http.Request) {
|
|
| 77 |
+ statusCode := GetHTTPErrorStatusCode(err) |
|
| 78 |
+ vars := mux.Vars(r) |
|
| 79 |
+ if vars["version"] == "" || versions.GreaterThan(vars["version"], "1.23") {
|
|
| 80 |
+ response := &types.ErrorResponse{
|
|
| 81 |
+ Message: err.Error(), |
|
| 82 |
+ } |
|
| 83 |
+ WriteJSON(w, statusCode, response) |
|
| 84 |
+ } else {
|
|
| 85 |
+ http.Error(w, err.Error(), statusCode) |
|
| 86 |
+ } |
|
| 78 | 87 |
} |
| 79 |
- |
|
| 80 |
- statusCode := GetHTTPErrorStatusCode(err) |
|
| 81 |
- http.Error(w, err.Error(), statusCode) |
|
| 82 | 88 |
} |
| ... | ... |
@@ -2,6 +2,7 @@ package server |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"crypto/tls" |
| 5 |
+ "fmt" |
|
| 5 | 6 |
"net" |
| 6 | 7 |
"net/http" |
| 7 | 8 |
"strings" |
| ... | ... |
@@ -10,6 +11,7 @@ import ( |
| 10 | 10 |
"github.com/docker/docker/api/server/httputils" |
| 11 | 11 |
"github.com/docker/docker/api/server/middleware" |
| 12 | 12 |
"github.com/docker/docker/api/server/router" |
| 13 |
+ "github.com/docker/docker/errors" |
|
| 13 | 14 |
"github.com/gorilla/mux" |
| 14 | 15 |
"golang.org/x/net/context" |
| 15 | 16 |
) |
| ... | ... |
@@ -136,7 +138,7 @@ func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc {
|
| 136 | 136 |
|
| 137 | 137 |
if err := handlerFunc(ctx, w, r, vars); err != nil {
|
| 138 | 138 |
logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err)
|
| 139 |
- httputils.WriteError(w, err) |
|
| 139 |
+ httputils.MakeErrorHandler(err)(w, r) |
|
| 140 | 140 |
} |
| 141 | 141 |
} |
| 142 | 142 |
} |
| ... | ... |
@@ -172,6 +174,11 @@ func (s *Server) createMux() *mux.Router {
|
| 172 | 172 |
} |
| 173 | 173 |
} |
| 174 | 174 |
|
| 175 |
+ err := errors.NewRequestNotFoundError(fmt.Errorf("page not found"))
|
|
| 176 |
+ notFoundHandler := httputils.MakeErrorHandler(err) |
|
| 177 |
+ m.HandleFunc(versionMatcher+"/{path:.*}", notFoundHandler)
|
|
| 178 |
+ m.NotFoundHandler = notFoundHandler |
|
| 179 |
+ |
|
| 175 | 180 |
return m |
| 176 | 181 |
} |
| 177 | 182 |
|
| ... | ... |
@@ -131,6 +131,7 @@ This section lists each version from latest to oldest. Each listing includes a |
| 131 | 131 |
* `POST /images/(name)/tag` no longer has a `force` query parameter. |
| 132 | 132 |
* `GET /images/search` now supports maximum returned search results `limit`. |
| 133 | 133 |
* `POST /containers/{name:.*}/copy` is now removed and errors out starting from this API version.
|
| 134 |
+* API errors are now returned as JSON instead of plain text. |
|
| 134 | 135 |
|
| 135 | 136 |
### v1.23 API changes |
| 136 | 137 |
|
| ... | ... |
@@ -262,4 +263,3 @@ end point now returns the new boolean fields `CpuCfsPeriod`, `CpuCfsQuota`, and |
| 262 | 262 |
* `CgroupParent` can be passed in the host config to setup container cgroups under a specific cgroup. |
| 263 | 263 |
* `POST /build` closing the HTTP request cancels the build |
| 264 | 264 |
* `POST /containers/(id)/exec` includes `Warnings` field to response. |
| 265 |
- |
| ... | ... |
@@ -22,9 +22,19 @@ weight=-5 |
| 22 | 22 |
- When the client API version is newer than the daemon's, these calls return an HTTP |
| 23 | 23 |
`400 Bad Request` error message. |
| 24 | 24 |
|
| 25 |
-# 2. Endpoints |
|
| 25 |
+# 2. Errors |
|
| 26 | 26 |
|
| 27 |
-## 2.1 Containers |
|
| 27 |
+The Remote API uses standard HTTP status codes to indicate the success or failure of the API call. The body of the response will be JSON in the following format: |
|
| 28 |
+ |
|
| 29 |
+ {
|
|
| 30 |
+ "message": "page not found" |
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+The status codes that are returned for each endpoint are specified in the endpoint documentation below. |
|
| 34 |
+ |
|
| 35 |
+# 3. Endpoints |
|
| 36 |
+ |
|
| 37 |
+## 3.1 Containers |
|
| 28 | 38 |
|
| 29 | 39 |
### List containers |
| 30 | 40 |
|
| ... | ... |
@@ -1504,7 +1514,7 @@ Status Codes: |
| 1504 | 1504 |
- no such file or directory (**path** resource does not exist) |
| 1505 | 1505 |
- **500** – server error |
| 1506 | 1506 |
|
| 1507 |
-## 2.2 Images |
|
| 1507 |
+## 3.2 Images |
|
| 1508 | 1508 |
|
| 1509 | 1509 |
### List Images |
| 1510 | 1510 |
|
| ... | ... |
@@ -2112,7 +2122,7 @@ Status Codes: |
| 2112 | 2112 |
- **200** – no error |
| 2113 | 2113 |
- **500** – server error |
| 2114 | 2114 |
|
| 2115 |
-## 2.3 Misc |
|
| 2115 |
+## 3.3 Misc |
|
| 2116 | 2116 |
|
| 2117 | 2117 |
### Check auth configuration |
| 2118 | 2118 |
|
| ... | ... |
@@ -2834,7 +2844,7 @@ Status Codes: |
| 2834 | 2834 |
- **404** – no such exec instance |
| 2835 | 2835 |
- **500** - server error |
| 2836 | 2836 |
|
| 2837 |
-## 2.4 Volumes |
|
| 2837 |
+## 3.4 Volumes |
|
| 2838 | 2838 |
|
| 2839 | 2839 |
### List volumes |
| 2840 | 2840 |
|
| ... | ... |
@@ -2972,7 +2982,7 @@ Status Codes |
| 2972 | 2972 |
- **409** - volume is in use and cannot be removed |
| 2973 | 2973 |
- **500** - server error |
| 2974 | 2974 |
|
| 2975 |
-## 2.5 Networks |
|
| 2975 |
+## 3.5 Networks |
|
| 2976 | 2976 |
|
| 2977 | 2977 |
### List networks |
| 2978 | 2978 |
|
| ... | ... |
@@ -3296,9 +3306,9 @@ Status Codes |
| 3296 | 3296 |
- **404** - no such network |
| 3297 | 3297 |
- **500** - server error |
| 3298 | 3298 |
|
| 3299 |
-# 3. Going further |
|
| 3299 |
+# 4. Going further |
|
| 3300 | 3300 |
|
| 3301 |
-## 3.1 Inside `docker run` |
|
| 3301 |
+## 4.1 Inside `docker run` |
|
| 3302 | 3302 |
|
| 3303 | 3303 |
As an example, the `docker run` command line makes the following API calls: |
| 3304 | 3304 |
|
| ... | ... |
@@ -3316,7 +3326,7 @@ As an example, the `docker run` command line makes the following API calls: |
| 3316 | 3316 |
|
| 3317 | 3317 |
- If in detached mode or only `stdin` is attached, display the container's id. |
| 3318 | 3318 |
|
| 3319 |
-## 3.2 Hijacking |
|
| 3319 |
+## 4.2 Hijacking |
|
| 3320 | 3320 |
|
| 3321 | 3321 |
In this version of the API, `/attach`, uses hijacking to transport `stdin`, |
| 3322 | 3322 |
`stdout`, and `stderr` on the same socket. |
| ... | ... |
@@ -3331,7 +3341,7 @@ When Docker daemon detects the `Upgrade` header, it switches its status code |
| 3331 | 3331 |
from **200 OK** to **101 UPGRADED** and resends the same headers. |
| 3332 | 3332 |
|
| 3333 | 3333 |
|
| 3334 |
-## 3.3 CORS Requests |
|
| 3334 |
+## 4.3 CORS Requests |
|
| 3335 | 3335 |
|
| 3336 | 3336 |
To set cross origin requests to the remote api please give values to |
| 3337 | 3337 |
`--api-cors-header` when running Docker in daemon mode. Set * (asterisk) allows all, |
| ... | ... |
@@ -85,8 +85,8 @@ func (s *DockerSuite) TestGetContainersWsAttachContainerNotFound(c *check.C) {
|
| 85 | 85 |
status, body, err := sockRequest("GET", "/containers/doesnotexist/attach/ws", nil)
|
| 86 | 86 |
c.Assert(status, checker.Equals, http.StatusNotFound) |
| 87 | 87 |
c.Assert(err, checker.IsNil) |
| 88 |
- expected := "No such container: doesnotexist\n" |
|
| 89 |
- c.Assert(string(body), checker.Contains, expected) |
|
| 88 |
+ expected := "No such container: doesnotexist" |
|
| 89 |
+ c.Assert(getErrorMessage(c, body), checker.Contains, expected) |
|
| 90 | 90 |
} |
| 91 | 91 |
|
| 92 | 92 |
func (s *DockerSuite) TestPostContainersAttach(c *check.C) {
|
| ... | ... |
@@ -16,9 +16,10 @@ func (s *DockerSuite) TestAuthApi(c *check.C) {
|
| 16 | 16 |
Password: "no-password", |
| 17 | 17 |
} |
| 18 | 18 |
|
| 19 |
- expected := "Get https://registry-1.docker.io/v2/: unauthorized: incorrect username or password\n" |
|
| 19 |
+ expected := "Get https://registry-1.docker.io/v2/: unauthorized: incorrect username or password" |
|
| 20 | 20 |
status, body, err := sockRequest("POST", "/auth", config)
|
| 21 | 21 |
c.Assert(err, check.IsNil) |
| 22 | 22 |
c.Assert(status, check.Equals, http.StatusUnauthorized) |
| 23 |
- c.Assert(string(body), checker.Contains, expected, check.Commentf("Expected: %v, got: %v", expected, string(body)))
|
|
| 23 |
+ msg := getErrorMessage(c, body) |
|
| 24 |
+ c.Assert(msg, checker.Contains, expected, check.Commentf("Expected: %v, got: %v", expected, msg))
|
|
| 24 | 25 |
} |
| ... | ... |
@@ -480,10 +480,10 @@ func (s *DockerSuite) TestContainerApiBadPort(c *check.C) {
|
| 480 | 480 |
jsonData := bytes.NewBuffer(nil) |
| 481 | 481 |
json.NewEncoder(jsonData).Encode(config) |
| 482 | 482 |
|
| 483 |
- status, b, err := sockRequest("POST", "/containers/create", config)
|
|
| 483 |
+ status, body, err := sockRequest("POST", "/containers/create", config)
|
|
| 484 | 484 |
c.Assert(err, checker.IsNil) |
| 485 | 485 |
c.Assert(status, checker.Equals, http.StatusInternalServerError) |
| 486 |
- c.Assert(strings.TrimSpace(string(b)), checker.Equals, `Invalid port specification: "aa80"`, check.Commentf("Incorrect error msg: %s", string(b)))
|
|
| 486 |
+ c.Assert(getErrorMessage(c, body), checker.Equals, `Invalid port specification: "aa80"`, check.Commentf("Incorrect error msg: %s", body))
|
|
| 487 | 487 |
} |
| 488 | 488 |
|
| 489 | 489 |
func (s *DockerSuite) TestContainerApiCreate(c *check.C) {
|
| ... | ... |
@@ -509,12 +509,12 @@ func (s *DockerSuite) TestContainerApiCreate(c *check.C) {
|
| 509 | 509 |
func (s *DockerSuite) TestContainerApiCreateEmptyConfig(c *check.C) {
|
| 510 | 510 |
config := map[string]interface{}{}
|
| 511 | 511 |
|
| 512 |
- status, b, err := sockRequest("POST", "/containers/create", config)
|
|
| 512 |
+ status, body, err := sockRequest("POST", "/containers/create", config)
|
|
| 513 | 513 |
c.Assert(err, checker.IsNil) |
| 514 | 514 |
c.Assert(status, checker.Equals, http.StatusInternalServerError) |
| 515 | 515 |
|
| 516 |
- expected := "Config cannot be empty in order to create a container\n" |
|
| 517 |
- c.Assert(string(b), checker.Equals, expected) |
|
| 516 |
+ expected := "Config cannot be empty in order to create a container" |
|
| 517 |
+ c.Assert(getErrorMessage(c, body), checker.Equals, expected) |
|
| 518 | 518 |
} |
| 519 | 519 |
|
| 520 | 520 |
func (s *DockerSuite) TestContainerApiCreateMultipleNetworksConfig(c *check.C) {
|
| ... | ... |
@@ -530,14 +530,15 @@ func (s *DockerSuite) TestContainerApiCreateMultipleNetworksConfig(c *check.C) {
|
| 530 | 530 |
}, |
| 531 | 531 |
} |
| 532 | 532 |
|
| 533 |
- status, b, err := sockRequest("POST", "/containers/create", config)
|
|
| 533 |
+ status, body, err := sockRequest("POST", "/containers/create", config)
|
|
| 534 | 534 |
c.Assert(err, checker.IsNil) |
| 535 | 535 |
c.Assert(status, checker.Equals, http.StatusBadRequest) |
| 536 |
+ msg := getErrorMessage(c, body) |
|
| 536 | 537 |
// network name order in error message is not deterministic |
| 537 |
- c.Assert(string(b), checker.Contains, "Container cannot be connected to network endpoints") |
|
| 538 |
- c.Assert(string(b), checker.Contains, "net1") |
|
| 539 |
- c.Assert(string(b), checker.Contains, "net2") |
|
| 540 |
- c.Assert(string(b), checker.Contains, "net3") |
|
| 538 |
+ c.Assert(msg, checker.Contains, "Container cannot be connected to network endpoints") |
|
| 539 |
+ c.Assert(msg, checker.Contains, "net1") |
|
| 540 |
+ c.Assert(msg, checker.Contains, "net2") |
|
| 541 |
+ c.Assert(msg, checker.Contains, "net3") |
|
| 541 | 542 |
} |
| 542 | 543 |
|
| 543 | 544 |
func (s *DockerSuite) TestContainerApiCreateWithHostName(c *check.C) {
|
| ... | ... |
@@ -997,7 +998,7 @@ func (s *DockerSuite) TestContainerApiDeleteNotExist(c *check.C) {
|
| 997 | 997 |
status, body, err := sockRequest("DELETE", "/containers/doesnotexist", nil)
|
| 998 | 998 |
c.Assert(err, checker.IsNil) |
| 999 | 999 |
c.Assert(status, checker.Equals, http.StatusNotFound) |
| 1000 |
- c.Assert(string(body), checker.Matches, "No such container: doesnotexist\n") |
|
| 1000 |
+ c.Assert(getErrorMessage(c, body), checker.Matches, "No such container: doesnotexist") |
|
| 1001 | 1001 |
} |
| 1002 | 1002 |
|
| 1003 | 1003 |
func (s *DockerSuite) TestContainerApiDeleteForce(c *check.C) {
|
| ... | ... |
@@ -1247,8 +1248,8 @@ func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *check.C) |
| 1247 | 1247 |
status, body, err := sockRequest("POST", "/containers/create?name="+name, c1)
|
| 1248 | 1248 |
c.Assert(err, checker.IsNil) |
| 1249 | 1249 |
c.Assert(status, checker.Equals, http.StatusInternalServerError) |
| 1250 |
- expected := "Invalid value 1-42,, for cpuset cpus\n" |
|
| 1251 |
- c.Assert(string(body), checker.Equals, expected) |
|
| 1250 |
+ expected := "Invalid value 1-42,, for cpuset cpus" |
|
| 1251 |
+ c.Assert(getErrorMessage(c, body), checker.Equals, expected) |
|
| 1252 | 1252 |
|
| 1253 | 1253 |
c2 := struct {
|
| 1254 | 1254 |
Image string |
| ... | ... |
@@ -1258,8 +1259,8 @@ func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *check.C) |
| 1258 | 1258 |
status, body, err = sockRequest("POST", "/containers/create?name="+name, c2)
|
| 1259 | 1259 |
c.Assert(err, checker.IsNil) |
| 1260 | 1260 |
c.Assert(status, checker.Equals, http.StatusInternalServerError) |
| 1261 |
- expected = "Invalid value 42-3,1-- for cpuset mems\n" |
|
| 1262 |
- c.Assert(string(body), checker.Equals, expected) |
|
| 1261 |
+ expected = "Invalid value 42-3,1-- for cpuset mems" |
|
| 1262 |
+ c.Assert(getErrorMessage(c, body), checker.Equals, expected) |
|
| 1263 | 1263 |
} |
| 1264 | 1264 |
|
| 1265 | 1265 |
func (s *DockerSuite) TestPostContainersCreateShmSizeNegative(c *check.C) {
|
| ... | ... |
@@ -1273,7 +1274,7 @@ func (s *DockerSuite) TestPostContainersCreateShmSizeNegative(c *check.C) {
|
| 1273 | 1273 |
status, body, err := sockRequest("POST", "/containers/create", config)
|
| 1274 | 1274 |
c.Assert(err, check.IsNil) |
| 1275 | 1275 |
c.Assert(status, check.Equals, http.StatusInternalServerError) |
| 1276 |
- c.Assert(string(body), checker.Contains, "SHM size must be greater than 0") |
|
| 1276 |
+ c.Assert(getErrorMessage(c, body), checker.Contains, "SHM size must be greater than 0") |
|
| 1277 | 1277 |
} |
| 1278 | 1278 |
|
| 1279 | 1279 |
func (s *DockerSuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *check.C) {
|
| ... | ... |
@@ -1409,9 +1410,11 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *che |
| 1409 | 1409 |
status, b, err := sockRequest("POST", "/containers/create?name="+name, config)
|
| 1410 | 1410 |
c.Assert(err, check.IsNil) |
| 1411 | 1411 |
c.Assert(status, check.Equals, http.StatusInternalServerError) |
| 1412 |
+ |
|
| 1412 | 1413 |
expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]" |
| 1413 |
- if !strings.Contains(string(b), expected) {
|
|
| 1414 |
- c.Fatalf("Expected output to contain %q, got %q", expected, string(b))
|
|
| 1414 |
+ msg := getErrorMessage(c, b) |
|
| 1415 |
+ if !strings.Contains(msg, expected) {
|
|
| 1416 |
+ c.Fatalf("Expected output to contain %q, got %q", expected, msg)
|
|
| 1415 | 1417 |
} |
| 1416 | 1418 |
|
| 1417 | 1419 |
config = struct {
|
| ... | ... |
@@ -1423,8 +1426,9 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *che |
| 1423 | 1423 |
c.Assert(err, check.IsNil) |
| 1424 | 1424 |
c.Assert(status, check.Equals, http.StatusInternalServerError) |
| 1425 | 1425 |
expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]" |
| 1426 |
- if !strings.Contains(string(b), expected) {
|
|
| 1427 |
- c.Fatalf("Expected output to contain %q, got %q", expected, string(b))
|
|
| 1426 |
+ msg = getErrorMessage(c, b) |
|
| 1427 |
+ if !strings.Contains(msg, expected) {
|
|
| 1428 |
+ c.Fatalf("Expected output to contain %q, got %q", expected, msg)
|
|
| 1428 | 1429 |
} |
| 1429 | 1430 |
} |
| 1430 | 1431 |
|
| ... | ... |
@@ -2,7 +2,6 @@ package main |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"net/http" |
| 5 |
- "strings" |
|
| 6 | 5 |
|
| 7 | 6 |
"github.com/docker/docker/pkg/integration/checker" |
| 8 | 7 |
"github.com/go-check/check" |
| ... | ... |
@@ -15,31 +14,31 @@ func (s *DockerSuite) TestApiCreateWithNotExistImage(c *check.C) {
|
| 15 | 15 |
"Volumes": map[string]struct{}{"/tmp": {}},
|
| 16 | 16 |
} |
| 17 | 17 |
|
| 18 |
- status, resp, err := sockRequest("POST", "/containers/create?name="+name, config)
|
|
| 18 |
+ status, body, err := sockRequest("POST", "/containers/create?name="+name, config)
|
|
| 19 | 19 |
c.Assert(err, check.IsNil) |
| 20 | 20 |
c.Assert(status, check.Equals, http.StatusNotFound) |
| 21 | 21 |
expected := "No such image: test456:v1" |
| 22 |
- c.Assert(strings.TrimSpace(string(resp)), checker.Contains, expected) |
|
| 22 |
+ c.Assert(getErrorMessage(c, body), checker.Contains, expected) |
|
| 23 | 23 |
|
| 24 | 24 |
config2 := map[string]interface{}{
|
| 25 | 25 |
"Image": "test456", |
| 26 | 26 |
"Volumes": map[string]struct{}{"/tmp": {}},
|
| 27 | 27 |
} |
| 28 | 28 |
|
| 29 |
- status, resp, err = sockRequest("POST", "/containers/create?name="+name, config2)
|
|
| 29 |
+ status, body, err = sockRequest("POST", "/containers/create?name="+name, config2)
|
|
| 30 | 30 |
c.Assert(err, check.IsNil) |
| 31 | 31 |
c.Assert(status, check.Equals, http.StatusNotFound) |
| 32 | 32 |
expected = "No such image: test456:latest" |
| 33 |
- c.Assert(strings.TrimSpace(string(resp)), checker.Equals, expected) |
|
| 33 |
+ c.Assert(getErrorMessage(c, body), checker.Equals, expected) |
|
| 34 | 34 |
|
| 35 | 35 |
config3 := map[string]interface{}{
|
| 36 | 36 |
"Image": "sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa", |
| 37 | 37 |
} |
| 38 | 38 |
|
| 39 |
- status, resp, err = sockRequest("POST", "/containers/create?name="+name, config3)
|
|
| 39 |
+ status, body, err = sockRequest("POST", "/containers/create?name="+name, config3)
|
|
| 40 | 40 |
c.Assert(err, check.IsNil) |
| 41 | 41 |
c.Assert(status, check.Equals, http.StatusNotFound) |
| 42 | 42 |
expected = "No such image: sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa" |
| 43 |
- c.Assert(strings.TrimSpace(string(resp)), checker.Equals, expected) |
|
| 43 |
+ c.Assert(getErrorMessage(c, body), checker.Equals, expected) |
|
| 44 | 44 |
|
| 45 | 45 |
} |
| ... | ... |
@@ -24,7 +24,7 @@ func (s *DockerSuite) TestExecApiCreateNoCmd(c *check.C) {
|
| 24 | 24 |
c.Assert(status, checker.Equals, http.StatusInternalServerError) |
| 25 | 25 |
|
| 26 | 26 |
comment := check.Commentf("Expected message when creating exec command with no Cmd specified")
|
| 27 |
- c.Assert(string(body), checker.Contains, "No exec command specified", comment) |
|
| 27 |
+ c.Assert(getErrorMessage(c, body), checker.Contains, "No exec command specified", comment) |
|
| 28 | 28 |
} |
| 29 | 29 |
|
| 30 | 30 |
func (s *DockerSuite) TestExecApiCreateNoValidContentType(c *check.C) {
|
| ... | ... |
@@ -44,7 +44,7 @@ func (s *DockerSuite) TestExecApiCreateNoValidContentType(c *check.C) {
|
| 44 | 44 |
c.Assert(err, checker.IsNil) |
| 45 | 45 |
|
| 46 | 46 |
comment := check.Commentf("Expected message when creating exec command with invalid Content-Type specified")
|
| 47 |
- c.Assert(string(b), checker.Contains, "Content-Type specified", comment) |
|
| 47 |
+ c.Assert(getErrorMessage(c, b), checker.Contains, "Content-Type specified", comment) |
|
| 48 | 48 |
} |
| 49 | 49 |
|
| 50 | 50 |
func (s *DockerSuite) TestExecApiCreateContainerPaused(c *check.C) {
|
| ... | ... |
@@ -59,7 +59,7 @@ func (s *DockerSuite) TestExecApiCreateContainerPaused(c *check.C) {
|
| 59 | 59 |
c.Assert(status, checker.Equals, http.StatusConflict) |
| 60 | 60 |
|
| 61 | 61 |
comment := check.Commentf("Expected message when creating exec command with Container %s is paused", name)
|
| 62 |
- c.Assert(string(body), checker.Contains, "Container "+name+" is paused, unpause the container before exec", comment) |
|
| 62 |
+ c.Assert(getErrorMessage(c, body), checker.Contains, "Container "+name+" is paused, unpause the container before exec", comment) |
|
| 63 | 63 |
} |
| 64 | 64 |
|
| 65 | 65 |
func (s *DockerSuite) TestExecApiStart(c *check.C) {
|
| ... | ... |
@@ -60,9 +60,7 @@ func (s *DockerSuite) TestLogsApiNoStdoutNorStderr(c *check.C) {
|
| 60 | 60 |
c.Assert(err, checker.IsNil) |
| 61 | 61 |
|
| 62 | 62 |
expected := "Bad parameters: you must choose at least one stream" |
| 63 |
- if !bytes.Contains(body, []byte(expected)) {
|
|
| 64 |
- c.Fatalf("Expected %s, got %s", expected, string(body[:]))
|
|
| 65 |
- } |
|
| 63 |
+ c.Assert(getErrorMessage(c, body), checker.Contains, expected) |
|
| 66 | 64 |
} |
| 67 | 65 |
|
| 68 | 66 |
// Regression test for #12704 |
| ... | ... |
@@ -40,5 +40,5 @@ func (s *DockerSuite) TestResizeApiResponseWhenContainerNotStarted(c *check.C) {
|
| 40 | 40 |
c.Assert(status, check.Equals, http.StatusInternalServerError) |
| 41 | 41 |
c.Assert(err, check.IsNil) |
| 42 | 42 |
|
| 43 |
- c.Assert(string(body), checker.Contains, "is not running", check.Commentf("resize should fail with message 'Container is not running'"))
|
|
| 43 |
+ c.Assert(getErrorMessage(c, body), checker.Contains, "is not running", check.Commentf("resize should fail with message 'Container is not running'"))
|
|
| 44 | 44 |
} |
| ... | ... |
@@ -60,7 +60,7 @@ func (s *DockerSuite) TestApiClientVersionNewerThanServer(c *check.C) {
|
| 60 | 60 |
c.Assert(err, checker.IsNil) |
| 61 | 61 |
c.Assert(status, checker.Equals, http.StatusBadRequest) |
| 62 | 62 |
expected := fmt.Sprintf("client is newer than server (client API version: %s, server API version: %s)", version, api.DefaultVersion)
|
| 63 |
- c.Assert(strings.TrimSpace(string(body)), checker.Equals, expected) |
|
| 63 |
+ c.Assert(getErrorMessage(c, body), checker.Equals, expected) |
|
| 64 | 64 |
} |
| 65 | 65 |
|
| 66 | 66 |
func (s *DockerSuite) TestApiClientVersionOldNotSupported(c *check.C) {
|
| ... | ... |
@@ -99,3 +99,44 @@ func (s *DockerSuite) TestApiDockerApiVersion(c *check.C) {
|
| 99 | 99 |
c.Fatalf("Out didn't have 'xxx' for the API version, had:\n%s", out)
|
| 100 | 100 |
} |
| 101 | 101 |
} |
| 102 |
+ |
|
| 103 |
+func (s *DockerSuite) TestApiErrorJSON(c *check.C) {
|
|
| 104 |
+ httpResp, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(`{}`), "application/json")
|
|
| 105 |
+ c.Assert(err, checker.IsNil) |
|
| 106 |
+ c.Assert(httpResp.StatusCode, checker.Equals, http.StatusInternalServerError) |
|
| 107 |
+ c.Assert(httpResp.Header.Get("Content-Type"), checker.Equals, "application/json")
|
|
| 108 |
+ b, err := readBody(body) |
|
| 109 |
+ c.Assert(err, checker.IsNil) |
|
| 110 |
+ c.Assert(getErrorMessage(c, b), checker.Equals, "Config cannot be empty in order to create a container") |
|
| 111 |
+} |
|
| 112 |
+ |
|
| 113 |
+func (s *DockerSuite) TestApiErrorPlainText(c *check.C) {
|
|
| 114 |
+ httpResp, body, err := sockRequestRaw("POST", "/v1.23/containers/create", strings.NewReader(`{}`), "application/json")
|
|
| 115 |
+ c.Assert(err, checker.IsNil) |
|
| 116 |
+ c.Assert(httpResp.StatusCode, checker.Equals, http.StatusInternalServerError) |
|
| 117 |
+ c.Assert(httpResp.Header.Get("Content-Type"), checker.Contains, "text/plain")
|
|
| 118 |
+ b, err := readBody(body) |
|
| 119 |
+ c.Assert(err, checker.IsNil) |
|
| 120 |
+ c.Assert(strings.TrimSpace(string(b)), checker.Equals, "Config cannot be empty in order to create a container") |
|
| 121 |
+} |
|
| 122 |
+ |
|
| 123 |
+func (s *DockerSuite) TestApiErrorNotFoundJSON(c *check.C) {
|
|
| 124 |
+ // 404 is a different code path to normal errors, so test separately |
|
| 125 |
+ httpResp, body, err := sockRequestRaw("GET", "/notfound", nil, "application/json")
|
|
| 126 |
+ c.Assert(err, checker.IsNil) |
|
| 127 |
+ c.Assert(httpResp.StatusCode, checker.Equals, http.StatusNotFound) |
|
| 128 |
+ c.Assert(httpResp.Header.Get("Content-Type"), checker.Equals, "application/json")
|
|
| 129 |
+ b, err := readBody(body) |
|
| 130 |
+ c.Assert(err, checker.IsNil) |
|
| 131 |
+ c.Assert(getErrorMessage(c, b), checker.Equals, "page not found") |
|
| 132 |
+} |
|
| 133 |
+ |
|
| 134 |
+func (s *DockerSuite) TestApiErrorNotFoundPlainText(c *check.C) {
|
|
| 135 |
+ httpResp, body, err := sockRequestRaw("GET", "/v1.23/notfound", nil, "application/json")
|
|
| 136 |
+ c.Assert(err, checker.IsNil) |
|
| 137 |
+ c.Assert(httpResp.StatusCode, checker.Equals, http.StatusNotFound) |
|
| 138 |
+ c.Assert(httpResp.Header.Get("Content-Type"), checker.Contains, "text/plain")
|
|
| 139 |
+ b, err := readBody(body) |
|
| 140 |
+ c.Assert(err, checker.IsNil) |
|
| 141 |
+ c.Assert(strings.TrimSpace(string(b)), checker.Equals, "page not found") |
|
| 142 |
+} |
| ... | ... |
@@ -1507,3 +1507,10 @@ func waitForGoroutines(expected int) error {
|
| 1507 | 1507 |
} |
| 1508 | 1508 |
} |
| 1509 | 1509 |
} |
| 1510 |
+ |
|
| 1511 |
+// getErrorMessage returns the error message from an error API response |
|
| 1512 |
+func getErrorMessage(c *check.C, body []byte) string {
|
|
| 1513 |
+ var resp types.ErrorResponse |
|
| 1514 |
+ c.Assert(json.Unmarshal(body, &resp), check.IsNil) |
|
| 1515 |
+ return strings.TrimSpace(resp.Message) |
|
| 1516 |
+} |