Merge pull request #51258 from thaJeztah/client_ping

api/types: move Ping and swarm.Status to client
This commit is contained in:
Sebastiaan van Stijn
2025-10-22 12:47:42 +02:00
committed by GitHub
16 changed files with 123 additions and 131 deletions

View File

@@ -214,16 +214,6 @@ type Info struct {
Warnings []string `json:",omitempty"`
}
// Status provides information about the current swarm status and role,
// obtained from the "Swarm" header in the API response.
type Status struct {
// NodeState represents the state of the node.
NodeState LocalNodeState
// ControlAvailable indicates if the node is a swarm manager.
ControlAvailable bool
}
// Peer represents a peer.
type Peer struct {
NodeID string

View File

@@ -1,10 +1,5 @@
package types
import (
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/swarm"
)
const (
// MediaTypeRawStream is vendor specific MIME-Type set for raw TTY streams
MediaTypeRawStream = "application/vnd.docker.raw-stream"
@@ -22,24 +17,6 @@ const (
MediaTypeJSONSequence = "application/json-seq"
)
// Ping contains response of Engine API:
// GET "/_ping"
type Ping struct {
APIVersion string
OSType string
Experimental bool
BuilderVersion build.BuilderVersion
// SwarmStatus provides information about the current swarm status of the
// engine, obtained from the "Swarm" header in the API response.
//
// It can be a nil struct if the API version does not provide this header
// in the ping response, or if an error occurred, in which case the client
// should use other ways to get the current swarm status, such as the /swarm
// endpoint.
SwarmStatus *swarm.Status
}
// ComponentVersion describes the version information for a specific component.
type ComponentVersion struct {
Name string

View File

@@ -56,7 +56,6 @@ import (
cerrdefs "github.com/containerd/errdefs"
"github.com/docker/go-connections/sockets"
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/versions"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
@@ -270,7 +269,7 @@ func (cli *Client) checkVersion(ctx context.Context) error {
return nil
}
ping, err := cli.Ping(ctx)
ping, err := cli.Ping(ctx, PingOptions{})
if err != nil {
return err
}
@@ -317,7 +316,7 @@ func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
cli.negotiateLock.Lock()
defer cli.negotiateLock.Unlock()
ping, err := cli.Ping(ctx)
ping, err := cli.Ping(ctx, PingOptions{})
if err != nil {
// FIXME(thaJeztah): Ping returns an error when failing to connect to the API; we should not swallow the error here, and instead returning it.
return
@@ -338,7 +337,8 @@ func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
//
// If the API server's ping response does not contain an API version, it falls
// back to the oldest API version supported.
func (cli *Client) NegotiateAPIVersionPing(pingResponse types.Ping) {
func (cli *Client) NegotiateAPIVersionPing(pingResponse PingResult) {
// TODO(thaJeztah): should this take a "Ping" option? It only consumes the version. This method should be removed overall and not be exported.
if !cli.manualOverride {
// Avoid concurrent modification of version-related fields
cli.negotiateLock.Lock()

View File

@@ -35,7 +35,7 @@ type stableAPIClient interface {
DaemonHost() string
ServerVersion(ctx context.Context) (types.Version, error)
NegotiateAPIVersion(ctx context.Context)
NegotiateAPIVersionPing(types.Ping)
NegotiateAPIVersionPing(PingResult)
HijackDialer
Dialer() func(context.Context) (net.Conn, error)
Close() error
@@ -187,7 +187,7 @@ type SystemAPIClient interface {
Info(ctx context.Context) (system.Info, error)
RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error)
DiskUsage(ctx context.Context, options DiskUsageOptions) (system.DiskUsage, error)
Ping(ctx context.Context) (types.Ping, error)
Ping(ctx context.Context, options PingOptions) (PingResult, error)
}
// VolumeAPIClient defines API client methods for the volumes

View File

@@ -11,7 +11,6 @@ import (
"strings"
"testing"
"github.com/moby/moby/api/types"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/skip"
@@ -267,7 +266,7 @@ func TestNegotiateAPIVersionEmpty(t *testing.T) {
const expected = fallbackAPIVersion
// test downgrade
client.NegotiateAPIVersionPing(types.Ping{})
client.NegotiateAPIVersionPing(PingResult{})
assert.Check(t, is.Equal(client.ClientVersion(), expected))
}
@@ -330,7 +329,7 @@ func TestNegotiateAPIVersion(t *testing.T) {
}
client, err := NewClientWithOpts(opts...)
assert.NilError(t, err)
client.NegotiateAPIVersionPing(types.Ping{APIVersion: tc.pingVersion})
client.NegotiateAPIVersionPing(PingResult{APIVersion: tc.pingVersion})
assert.Check(t, is.Equal(tc.expectedVersion, client.ClientVersion()))
})
}
@@ -346,7 +345,7 @@ func TestNegotiateAPIVersionOverride(t *testing.T) {
assert.NilError(t, err)
// test that we honored the env var
client.NegotiateAPIVersionPing(types.Ping{APIVersion: "1.24"})
client.NegotiateAPIVersionPing(PingResult{APIVersion: "1.24"})
assert.Check(t, is.Equal(client.ClientVersion(), expected))
}
@@ -404,7 +403,7 @@ func TestNegotiateAPIVersionWithEmptyVersion(t *testing.T) {
assert.NilError(t, err)
const expected = "1.50"
client.NegotiateAPIVersionPing(types.Ping{APIVersion: expected})
client.NegotiateAPIVersionPing(PingResult{APIVersion: expected})
assert.Check(t, is.Equal(client.ClientVersion(), expected))
}
@@ -415,7 +414,7 @@ func TestNegotiateAPIVersionWithFixedVersion(t *testing.T) {
client, err := NewClientWithOpts(WithVersion(customVersion))
assert.NilError(t, err)
client.NegotiateAPIVersionPing(types.Ping{APIVersion: "1.49"})
client.NegotiateAPIVersionPing(PingResult{APIVersion: "1.49"})
assert.Check(t, is.Equal(client.ClientVersion(), customVersion))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"runtime"
"testing"
@@ -72,7 +71,7 @@ func TestWithUserAgent(t *testing.T) {
}),
)
assert.NilError(t, err)
_, err = c.Ping(context.Background())
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
@@ -87,7 +86,7 @@ func TestWithUserAgent(t *testing.T) {
}),
)
assert.NilError(t, err)
_, err = c.Ping(context.Background())
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
@@ -101,7 +100,7 @@ func TestWithUserAgent(t *testing.T) {
}),
)
assert.NilError(t, err)
_, err = c.Ping(context.Background())
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
@@ -115,7 +114,7 @@ func TestWithUserAgent(t *testing.T) {
}),
)
assert.NilError(t, err)
_, err = c.Ping(context.Background())
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
@@ -130,7 +129,7 @@ func TestWithUserAgent(t *testing.T) {
}),
)
assert.NilError(t, err)
_, err = c.Ping(context.Background())
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})

View File

@@ -6,11 +6,42 @@ import (
"path"
"strings"
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/swarm"
)
// PingOptions holds options for [client.Ping].
type PingOptions struct {
// Add future optional parameters here
}
// PingResult holds the result of a [Client.Ping] API call.
type PingResult struct {
APIVersion string
OSType string
Experimental bool
BuilderVersion build.BuilderVersion
// SwarmStatus provides information about the current swarm status of the
// engine, obtained from the "Swarm" header in the API response.
//
// It can be a nil struct if the API version does not provide this header
// in the ping response, or if an error occurred, in which case the client
// should use other ways to get the current swarm status, such as the /swarm
// endpoint.
SwarmStatus *SwarmStatus
}
// SwarmStatus provides information about the current swarm status and role,
// obtained from the "Swarm" header in the API response.
type SwarmStatus struct {
// NodeState represents the state of the node.
NodeState swarm.LocalNodeState
// ControlAvailable indicates if the node is a swarm manager.
ControlAvailable bool
}
// 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
@@ -18,13 +49,13 @@ import (
// 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) {
func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, error) {
// 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 types.Ping{}, err
return PingResult{}, err
}
resp, err := cli.doRequest(req)
defer ensureReaderClosed(resp)
@@ -33,7 +64,7 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
// we got a "OK" (200) status. For non-200 status-codes, we fall
// back to doing a GET request, as a HEAD request won't have a
// response-body to get error details from.
return newPingResponse(resp), nil
return newPingResult(resp), nil
}
// HEAD failed or returned a non-OK status; fallback to GET.
@@ -42,29 +73,29 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
defer ensureReaderClosed(resp)
if err != nil {
// Failed to connect.
return types.Ping{}, err
return PingResult{}, err
}
// GET request succeeded but may have returned a non-200 status.
// Return a Ping response, together with any error returned by
// the API server.
return newPingResponse(resp), checkResponseErr(resp)
return newPingResult(resp), checkResponseErr(resp)
}
func newPingResponse(resp *http.Response) types.Ping {
func newPingResult(resp *http.Response) PingResult {
if resp == nil {
return types.Ping{}
return PingResult{}
}
var swarmStatus *swarm.Status
var swarmStatus *SwarmStatus
if si := resp.Header.Get("Swarm"); si != "" {
state, role, _ := strings.Cut(si, "/")
swarmStatus = &swarm.Status{
swarmStatus = &SwarmStatus{
NodeState: swarm.LocalNodeState(state),
ControlAvailable: role == "manager",
}
}
return types.Ping{
return PingResult{
APIVersion: resp.Header.Get("Api-Version"),
OSType: resp.Header.Get("Ostype"),
Experimental: resp.Header.Get("Docker-Experimental") == "true",

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"errors"
"fmt"
"io"
@@ -10,7 +9,6 @@ import (
"strings"
"testing"
"github.com/moby/moby/api/types/swarm"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@@ -34,19 +32,19 @@ func TestPingFail(t *testing.T) {
}))
assert.NilError(t, err)
ping, err := client.Ping(context.Background())
ping, err := client.Ping(t.Context(), PingOptions{})
assert.Check(t, is.ErrorContains(err, "some error with the server"))
assert.Check(t, is.Equal(false, ping.Experimental))
assert.Check(t, is.Equal("", ping.APIVersion))
var si *swarm.Status
var si *SwarmStatus
assert.Check(t, is.Equal(si, ping.SwarmStatus))
withHeader = true
ping2, err := client.Ping(context.Background())
ping2, err := client.Ping(t.Context(), PingOptions{})
assert.Check(t, is.ErrorContains(err, "some error with the server"))
assert.Check(t, is.Equal(true, ping2.Experimental))
assert.Check(t, is.Equal("awesome", ping2.APIVersion))
assert.Check(t, is.Equal(swarm.Status{NodeState: "inactive"}, *ping2.SwarmStatus))
assert.Check(t, is.Equal(SwarmStatus{NodeState: "inactive"}, *ping2.SwarmStatus))
}
// TestPingWithError tests the case where there is a protocol error in the ping.
@@ -57,11 +55,11 @@ func TestPingWithError(t *testing.T) {
}))
assert.NilError(t, err)
ping, err := client.Ping(context.Background())
ping, err := client.Ping(t.Context(), PingOptions{})
assert.Check(t, is.ErrorContains(err, "some connection error"))
assert.Check(t, is.Equal(false, ping.Experimental))
assert.Check(t, is.Equal("", ping.APIVersion))
var si *swarm.Status
var si *SwarmStatus
assert.Check(t, is.Equal(si, ping.SwarmStatus))
}
@@ -78,11 +76,11 @@ func TestPingSuccess(t *testing.T) {
return resp, nil
}))
assert.NilError(t, err)
ping, err := client.Ping(context.Background())
ping, err := client.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(true, ping.Experimental))
assert.Check(t, is.Equal("awesome", ping.APIVersion))
assert.Check(t, is.Equal(swarm.Status{NodeState: "active", ControlAvailable: true}, *ping.SwarmStatus))
assert.Check(t, is.Equal(SwarmStatus{NodeState: "active", ControlAvailable: true}, *ping.SwarmStatus))
}
// TestPingHeadFallback tests that the client falls back to GET if HEAD fails.
@@ -131,7 +129,7 @@ func TestPingHeadFallback(t *testing.T) {
return resp, nil
}))
assert.NilError(t, err)
ping, _ := client.Ping(context.Background())
ping, _ := client.Ping(t.Context(), PingOptions{})
assert.Check(t, is.Equal(ping.APIVersion, "1.2.3"))
assert.Check(t, is.DeepEqual(reqs, tc.expected))
})

View File

@@ -210,7 +210,7 @@ func TestResponseErrors(t *testing.T) {
client, err = NewClientWithOpts(WithHTTPClient(client.client), WithVersion(tc.apiVersion))
}
assert.NilError(t, err)
_, err = client.Ping(context.Background())
_, err = client.Ping(t.Context(), PingOptions{})
assert.Check(t, is.Error(err, tc.expected))
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
})
@@ -229,7 +229,7 @@ func TestInfiniteError(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.Ping(context.Background())
_, err = client.Ping(t.Context(), PingOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
assert.Check(t, is.ErrorContains(err, "request returned Internal Server Error"))
}

View File

@@ -2235,7 +2235,7 @@ func (s *DockerDaemonSuite) TestFailedPluginRemove(c *testing.T) {
d.Restart(c)
ctx, cancel = context.WithTimeout(testutil.GetContext(c), 30*time.Second)
defer cancel()
_, err = apiClient.Ping(ctx)
_, err = apiClient.Ping(ctx, client.PingOptions{})
assert.NilError(c, err)
_, err = apiClient.PluginInspect(ctx, name, client.PluginInspectOptions{})

View File

@@ -68,7 +68,7 @@ func TestPingSwarmHeader(t *testing.T) {
t.Run("before swarm init", func(t *testing.T) {
ctx := testutil.StartSpan(ctx, t)
p, err := apiClient.Ping(ctx)
p, err := apiClient.Ping(ctx, client.PingOptions{})
assert.NilError(t, err)
assert.Equal(t, p.SwarmStatus.NodeState, swarm.LocalNodeStateInactive)
assert.Equal(t, p.SwarmStatus.ControlAvailable, false)
@@ -79,7 +79,7 @@ func TestPingSwarmHeader(t *testing.T) {
t.Run("after swarm init", func(t *testing.T) {
ctx := testutil.StartSpan(ctx, t)
p, err := apiClient.Ping(ctx)
p, err := apiClient.Ping(ctx, client.PingOptions{})
assert.NilError(t, err)
assert.Equal(t, p.SwarmStatus.NodeState, swarm.LocalNodeStateActive)
assert.Equal(t, p.SwarmStatus.ControlAvailable, true)
@@ -90,7 +90,7 @@ func TestPingSwarmHeader(t *testing.T) {
t.Run("after swarm leave", func(t *testing.T) {
ctx := testutil.StartSpan(ctx, t)
p, err := apiClient.Ping(ctx)
p, err := apiClient.Ping(ctx, client.PingOptions{})
assert.NilError(t, err)
assert.Equal(t, p.SwarmStatus.NodeState, swarm.LocalNodeStateInactive)
assert.Equal(t, p.SwarmStatus.ControlAvailable, false)
@@ -116,7 +116,7 @@ func TestPingBuilderHeader(t *testing.T) {
expected = build.BuilderV1
}
p, err := apiClient.Ping(ctx)
p, err := apiClient.Ping(ctx, client.PingOptions{})
assert.NilError(t, err)
assert.Equal(t, p.BuilderVersion, expected)
})
@@ -130,7 +130,7 @@ func TestPingBuilderHeader(t *testing.T) {
defer d.Stop(t)
expected := build.BuilderV1
p, err := apiClient.Ping(ctx)
p, err := apiClient.Ping(ctx, client.PingOptions{})
assert.NilError(t, err)
assert.Equal(t, p.BuilderVersion, expected)
})

View File

@@ -214,16 +214,6 @@ type Info struct {
Warnings []string `json:",omitempty"`
}
// Status provides information about the current swarm status and role,
// obtained from the "Swarm" header in the API response.
type Status struct {
// NodeState represents the state of the node.
NodeState LocalNodeState
// ControlAvailable indicates if the node is a swarm manager.
ControlAvailable bool
}
// Peer represents a peer.
type Peer struct {
NodeID string

View File

@@ -1,10 +1,5 @@
package types
import (
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/swarm"
)
const (
// MediaTypeRawStream is vendor specific MIME-Type set for raw TTY streams
MediaTypeRawStream = "application/vnd.docker.raw-stream"
@@ -22,24 +17,6 @@ const (
MediaTypeJSONSequence = "application/json-seq"
)
// Ping contains response of Engine API:
// GET "/_ping"
type Ping struct {
APIVersion string
OSType string
Experimental bool
BuilderVersion build.BuilderVersion
// SwarmStatus provides information about the current swarm status of the
// engine, obtained from the "Swarm" header in the API response.
//
// It can be a nil struct if the API version does not provide this header
// in the ping response, or if an error occurred, in which case the client
// should use other ways to get the current swarm status, such as the /swarm
// endpoint.
SwarmStatus *swarm.Status
}
// ComponentVersion describes the version information for a specific component.
type ComponentVersion struct {
Name string

View File

@@ -56,7 +56,6 @@ import (
cerrdefs "github.com/containerd/errdefs"
"github.com/docker/go-connections/sockets"
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/versions"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
@@ -270,7 +269,7 @@ func (cli *Client) checkVersion(ctx context.Context) error {
return nil
}
ping, err := cli.Ping(ctx)
ping, err := cli.Ping(ctx, PingOptions{})
if err != nil {
return err
}
@@ -317,7 +316,7 @@ func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
cli.negotiateLock.Lock()
defer cli.negotiateLock.Unlock()
ping, err := cli.Ping(ctx)
ping, err := cli.Ping(ctx, PingOptions{})
if err != nil {
// FIXME(thaJeztah): Ping returns an error when failing to connect to the API; we should not swallow the error here, and instead returning it.
return
@@ -338,7 +337,8 @@ func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
//
// If the API server's ping response does not contain an API version, it falls
// back to the oldest API version supported.
func (cli *Client) NegotiateAPIVersionPing(pingResponse types.Ping) {
func (cli *Client) NegotiateAPIVersionPing(pingResponse PingResult) {
// TODO(thaJeztah): should this take a "Ping" option? It only consumes the version. This method should be removed overall and not be exported.
if !cli.manualOverride {
// Avoid concurrent modification of version-related fields
cli.negotiateLock.Lock()

View File

@@ -35,7 +35,7 @@ type stableAPIClient interface {
DaemonHost() string
ServerVersion(ctx context.Context) (types.Version, error)
NegotiateAPIVersion(ctx context.Context)
NegotiateAPIVersionPing(types.Ping)
NegotiateAPIVersionPing(PingResult)
HijackDialer
Dialer() func(context.Context) (net.Conn, error)
Close() error
@@ -187,7 +187,7 @@ type SystemAPIClient interface {
Info(ctx context.Context) (system.Info, error)
RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error)
DiskUsage(ctx context.Context, options DiskUsageOptions) (system.DiskUsage, error)
Ping(ctx context.Context) (types.Ping, error)
Ping(ctx context.Context, options PingOptions) (PingResult, error)
}
// VolumeAPIClient defines API client methods for the volumes

View File

@@ -6,11 +6,42 @@ import (
"path"
"strings"
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/swarm"
)
// PingOptions holds options for [client.Ping].
type PingOptions struct {
// Add future optional parameters here
}
// PingResult holds the result of a [Client.Ping] API call.
type PingResult struct {
APIVersion string
OSType string
Experimental bool
BuilderVersion build.BuilderVersion
// SwarmStatus provides information about the current swarm status of the
// engine, obtained from the "Swarm" header in the API response.
//
// It can be a nil struct if the API version does not provide this header
// in the ping response, or if an error occurred, in which case the client
// should use other ways to get the current swarm status, such as the /swarm
// endpoint.
SwarmStatus *SwarmStatus
}
// SwarmStatus provides information about the current swarm status and role,
// obtained from the "Swarm" header in the API response.
type SwarmStatus struct {
// NodeState represents the state of the node.
NodeState swarm.LocalNodeState
// ControlAvailable indicates if the node is a swarm manager.
ControlAvailable bool
}
// 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
@@ -18,13 +49,13 @@ import (
// 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) {
func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, error) {
// 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 types.Ping{}, err
return PingResult{}, err
}
resp, err := cli.doRequest(req)
defer ensureReaderClosed(resp)
@@ -33,7 +64,7 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
// we got a "OK" (200) status. For non-200 status-codes, we fall
// back to doing a GET request, as a HEAD request won't have a
// response-body to get error details from.
return newPingResponse(resp), nil
return newPingResult(resp), nil
}
// HEAD failed or returned a non-OK status; fallback to GET.
@@ -42,29 +73,29 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
defer ensureReaderClosed(resp)
if err != nil {
// Failed to connect.
return types.Ping{}, err
return PingResult{}, err
}
// GET request succeeded but may have returned a non-200 status.
// Return a Ping response, together with any error returned by
// the API server.
return newPingResponse(resp), checkResponseErr(resp)
return newPingResult(resp), checkResponseErr(resp)
}
func newPingResponse(resp *http.Response) types.Ping {
func newPingResult(resp *http.Response) PingResult {
if resp == nil {
return types.Ping{}
return PingResult{}
}
var swarmStatus *swarm.Status
var swarmStatus *SwarmStatus
if si := resp.Header.Get("Swarm"); si != "" {
state, role, _ := strings.Cut(si, "/")
swarmStatus = &swarm.Status{
swarmStatus = &SwarmStatus{
NodeState: swarm.LocalNodeState(state),
ControlAvailable: role == "manager",
}
}
return types.Ping{
return PingResult{
APIVersion: resp.Header.Get("Api-Version"),
OSType: resp.Header.Get("Ostype"),
Experimental: resp.Header.Get("Docker-Experimental") == "true",