From 230f178f8bfe40c09018b8da4cbb990b55a81719 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 20 Mar 2025 12:37:21 +0100 Subject: [PATCH 1/2] api: return plain-text errors for deprecated API versions Docker 25.0 (08e4e88482d640b9543d4ba6332109e6b6f2495d) deprecated API versions older than v1.24, and support was removed in Docker 26.0. As part of this deprecation, support for plain-text errors was also removed in commit ffd877f94866975bd0c23c425d363a75610524d6. So while we no longer support API versions older 1.24 [api.MinSupportedAPIVersion], a client may try to connect using an older version and expect a plain-text error instead of a JSON error. This would result in an "API version too old" error formatted in JSON being printed as-is. DOCKER_API_VERSION=v1.10 docker info --format '{{.ID}}' Error response from daemon: {"message":"client version 1.10 is too old. Minimum supported API version is 1.24, please upgrade your client to a newer version"} curl --unix-socket /var/run/docker.sock http://localhost/v1.10/info {"message":"client version 1.10 is too old. Minimum supported API version is 1.24, please upgrade your client to a newer version"} Note that this was only a problem for old API versions; unsupported API versions that were higher than the maximum version were already handled as JSON; DOCKER_API_VERSION=v1.99 docker info --format '{{.ID}}' Error response from daemon: client version 1.99 is too new. Maximum supported API version is 1.48 curl --unix-socket /var/run/docker.sock http://localhost/v1.99/info {"message":"client version 1.99 is too new. Maximum supported API version is 1.48"} Let's be nice, and return errors in plain-text to provide a more readable error to help the user understand the API version they're using is no longer supported. With this patch applied: DOCKER_API_VERSION=v1.10 docker info --format '{{.ID}}' Error response from daemon: client version 1.10 is too old. Minimum supported API version is 1.24, please upgrade your client to a newer version curl --unix-socket /var/run/docker.sock http://localhost/v1.10/info client version 1.10 is too old. Minimum supported API version is 1.24, please upgrade your client to a newer version Signed-off-by: Sebastiaan van Stijn --- api/server/server.go | 18 +++++++++++++++--- integration-cli/docker_api_test.go | 8 +++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index a0a7e3bfa5..6d2351e55e 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -10,6 +10,7 @@ import ( "github.com/docker/docker/api/server/middleware" "github.com/docker/docker/api/server/router" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/dockerversion" "github.com/gorilla/mux" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" @@ -57,9 +58,20 @@ func (s *Server) makeHTTPHandler(handler httputils.APIFunc, operation string) ht if statusCode >= 500 { log.G(ctx).Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err) } - _ = httputils.WriteJSON(w, statusCode, &types.ErrorResponse{ - Message: err.Error(), - }) + // While we no longer support API versions older 1.24 [api.MinSupportedAPIVersion], + // a client may try to connect using an older version and expect a plain-text error + // instead of a JSON error. This would result in an "API version too old" error + // formatted in JSON being printed as-is. + // + // Let's be nice, and return errors in plain-text to provide a more readable error + // to help the user understand the API version they're using is no longer supported. + if v := vars["version"]; v != "" && versions.LessThan(v, "1.24") { + http.Error(w, err.Error(), statusCode) + } else { + _ = httputils.WriteJSON(w, statusCode, &types.ErrorResponse{ + Message: err.Error(), + }) + } } }), operation).ServeHTTP } diff --git a/integration-cli/docker_api_test.go b/integration-cli/docker_api_test.go index f00f02fad9..ef13dabf35 100644 --- a/integration-cli/docker_api_test.go +++ b/integration-cli/docker_api_test.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "context" "fmt" "net/http" @@ -9,6 +10,7 @@ import ( "strings" "testing" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/runconfig" "github.com/docker/docker/testutil" "github.com/docker/docker/testutil/request" @@ -64,7 +66,11 @@ func (s *DockerAPISuite) TestAPIClientVersionOldNotSupported(c *testing.T) { expected := fmt.Sprintf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", version, testEnv.DaemonVersion.MinAPIVersion) b, err := request.ReadBody(body) assert.NilError(c, err) - assert.Equal(c, getErrorMessage(c, b), expected) + errMessage := string(bytes.TrimSpace(b)) + if versions.GreaterThanOrEqualTo(version, "1.24") { + errMessage = getErrorMessage(c, b) + } + assert.Equal(c, errMessage, expected) } func (s *DockerAPISuite) TestAPIErrorJSON(c *testing.T) { From 126d4cf672a274c7af8c72e02f7aa37d64d80417 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 20 Mar 2025 12:42:20 +0100 Subject: [PATCH 2/2] client: remove version-gate for JSON response errors JSON errors were introduced in API 1.24, and daemons running older versions of the API would return errors as plain-text. However, such API versions would also send the corresponding content-type header (text/plain), so we don't really need to make the code version-dependent; there's already fallbacks in place to handle JSON-responses that don't use the expected format, in which case we produce a generic status-code error. Before this patch, the client would print JSON-responses as-is when the daemon returned an "API version too old" error; DOCKER_API_VERSION=v1.10 docker info --format '{{.ID}}' Error response from daemon: {"message":"client version 1.10 is too old. Minimum supported API version is 1.24, please upgrade your client to a newer version"} With this patch, the client detects that the response is JSON, and prints a friendlier error-message to help the user discover their client is too old; DOCKER_API_VERSION=v1.10 docker info --format '{{.ID}}' Error response from daemon: client version 1.10 is too old. Minimum supported API version is 1.24, please upgrade your client to a newer version Signed-off-by: Sebastiaan van Stijn --- client/request.go | 2 +- client/request_test.go | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/client/request.go b/client/request.go index 2b913aab64..4cc64350bd 100644 --- a/client/request.go +++ b/client/request.go @@ -237,7 +237,7 @@ func (cli *Client) checkResponseErr(serverResp *http.Response) (retErr error) { } var daemonErr error - if serverResp.Header.Get("Content-Type") == "application/json" && (cli.version == "" || versions.GreaterThan(cli.version, "1.23")) { + if serverResp.Header.Get("Content-Type") == "application/json" { var errorResponse types.ErrorResponse if err := json.Unmarshal(body, &errorResponse); err != nil { return errors.Wrap(err, "Error reading JSON") diff --git a/client/request_test.go b/client/request_test.go index 71a1bb6866..39b7439a34 100644 --- a/client/request_test.go +++ b/client/request_test.go @@ -132,12 +132,15 @@ func TestResponseErrors(t *testing.T) { expected: `Error response from daemon: Some error occurred`, }, { - // API versions before 1.24 did not support JSON errors, and return response as-is. + // API versions before 1.24 did not support JSON errors. Technically, + // we no longer downgrade to older API versions, but we make an + // exception for errors so that older clients would print a more + // readable error. doc: "JSON error on old API", apiVersion: "1.23", - contentType: "application/json", - response: `{"message":"Some error occurred"}`, - expected: `Error response from daemon: {"message":"Some error occurred"}`, + contentType: "text/plain; charset=utf-8", + response: `client version 1.10 is too old. Minimum supported API version is 1.24, please upgrade your client to a newer version`, + expected: `Error response from daemon: client version 1.10 is too old. Minimum supported API version is 1.24, please upgrade your client to a newer version`, }, { doc: "plain-text error",