3ec1946ce1
NegotiateAPIVersion was ignoring errors returned by Ping. The intent here
was to handle API responses from a daemon that may be in an unhealthy state,
however this case is already handled by Ping itself.
Ping only returns an error when either failing to connect to the API (daemon
not running or permissions errors), or when failing to parse the API response.
Neither of those should be ignored in this code, or considered a successful
"ping", so update the code to return
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 901b90593d
)
Signed-off-by: Bjorn Neergaard <bjorn.neergaard@docker.com>
76 lines
2.5 KiB
Go
76 lines
2.5 KiB
Go
package client // import "github.com/docker/docker/client"
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/swarm"
|
|
"github.com/docker/docker/errdefs"
|
|
)
|
|
|
|
// Ping pings the server and returns the value of the "Docker-Experimental",
|
|
// "Builder-Version", "OS-Type" & "API-Version" headers. It attempts to use
|
|
// a HEAD request on the endpoint, but falls back to GET if HEAD is not supported
|
|
// by the daemon. It ignores internal server errors returned by the API, which
|
|
// may be returned if the daemon is in an unhealthy state, but returns errors
|
|
// for other non-success status codes, failing to connect to the API, or failing
|
|
// to parse the API response.
|
|
func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
|
|
var ping types.Ping
|
|
|
|
// Using cli.buildRequest() + cli.doRequest() instead of cli.sendRequest()
|
|
// because ping requests are used during API version negotiation, so we want
|
|
// to hit the non-versioned /_ping endpoint, not /v1.xx/_ping
|
|
req, err := cli.buildRequest(ctx, http.MethodHead, path.Join(cli.basePath, "/_ping"), nil, nil)
|
|
if err != nil {
|
|
return ping, err
|
|
}
|
|
serverResp, err := cli.doRequest(req)
|
|
if err == nil {
|
|
defer ensureReaderClosed(serverResp)
|
|
switch serverResp.statusCode {
|
|
case http.StatusOK, http.StatusInternalServerError:
|
|
// Server handled the request, so parse the response
|
|
return parsePingResponse(cli, serverResp)
|
|
}
|
|
} else if IsErrConnectionFailed(err) {
|
|
return ping, err
|
|
}
|
|
|
|
// HEAD failed; fallback to GET.
|
|
req.Method = http.MethodGet
|
|
serverResp, err = cli.doRequest(req)
|
|
defer ensureReaderClosed(serverResp)
|
|
if err != nil {
|
|
return ping, err
|
|
}
|
|
return parsePingResponse(cli, serverResp)
|
|
}
|
|
|
|
func parsePingResponse(cli *Client, resp serverResponse) (types.Ping, error) {
|
|
var ping types.Ping
|
|
if resp.header == nil {
|
|
err := cli.checkResponseErr(resp)
|
|
return ping, errdefs.FromStatusCode(err, resp.statusCode)
|
|
}
|
|
ping.APIVersion = resp.header.Get("API-Version")
|
|
ping.OSType = resp.header.Get("OSType")
|
|
if resp.header.Get("Docker-Experimental") == "true" {
|
|
ping.Experimental = true
|
|
}
|
|
if bv := resp.header.Get("Builder-Version"); bv != "" {
|
|
ping.BuilderVersion = types.BuilderVersion(bv)
|
|
}
|
|
if si := resp.header.Get("Swarm"); si != "" {
|
|
state, role, _ := strings.Cut(si, "/")
|
|
ping.SwarmStatus = &swarm.Status{
|
|
NodeState: swarm.LocalNodeState(state),
|
|
ControlAvailable: role == "manager",
|
|
}
|
|
}
|
|
err := cli.checkResponseErr(resp)
|
|
return ping, errdefs.FromStatusCode(err, resp.statusCode)
|
|
}
|