Browse code

api/types: move Ping and swarm.Status to client

The API does not produce these as a response; the fields in the Ping
struct, including the Swarm status are propagated from headers returned
by the /_ping endpoint.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2025/10/22 17:19:54
Showing 16 changed files
... ...
@@ -214,16 +214,6 @@ type Info struct {
214 214
 	Warnings []string `json:",omitempty"`
215 215
 }
216 216
 
217
-// Status provides information about the current swarm status and role,
218
-// obtained from the "Swarm" header in the API response.
219
-type Status struct {
220
-	// NodeState represents the state of the node.
221
-	NodeState LocalNodeState
222
-
223
-	// ControlAvailable indicates if the node is a swarm manager.
224
-	ControlAvailable bool
225
-}
226
-
227 217
 // Peer represents a peer.
228 218
 type Peer struct {
229 219
 	NodeID string
... ...
@@ -1,10 +1,5 @@
1 1
 package types
2 2
 
3
-import (
4
-	"github.com/moby/moby/api/types/build"
5
-	"github.com/moby/moby/api/types/swarm"
6
-)
7
-
8 3
 const (
9 4
 	// MediaTypeRawStream is vendor specific MIME-Type set for raw TTY streams
10 5
 	MediaTypeRawStream = "application/vnd.docker.raw-stream"
... ...
@@ -22,24 +17,6 @@ const (
22 22
 	MediaTypeJSONSequence = "application/json-seq"
23 23
 )
24 24
 
25
-// Ping contains response of Engine API:
26
-// GET "/_ping"
27
-type Ping struct {
28
-	APIVersion     string
29
-	OSType         string
30
-	Experimental   bool
31
-	BuilderVersion build.BuilderVersion
32
-
33
-	// SwarmStatus provides information about the current swarm status of the
34
-	// engine, obtained from the "Swarm" header in the API response.
35
-	//
36
-	// It can be a nil struct if the API version does not provide this header
37
-	// in the ping response, or if an error occurred, in which case the client
38
-	// should use other ways to get the current swarm status, such as the /swarm
39
-	// endpoint.
40
-	SwarmStatus *swarm.Status
41
-}
42
-
43 25
 // ComponentVersion describes the version information for a specific component.
44 26
 type ComponentVersion struct {
45 27
 	Name    string
... ...
@@ -56,7 +56,6 @@ import (
56 56
 
57 57
 	cerrdefs "github.com/containerd/errdefs"
58 58
 	"github.com/docker/go-connections/sockets"
59
-	"github.com/moby/moby/api/types"
60 59
 	"github.com/moby/moby/api/types/versions"
61 60
 	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
62 61
 )
... ...
@@ -270,7 +269,7 @@ func (cli *Client) checkVersion(ctx context.Context) error {
270 270
 			return nil
271 271
 		}
272 272
 
273
-		ping, err := cli.Ping(ctx)
273
+		ping, err := cli.Ping(ctx, PingOptions{})
274 274
 		if err != nil {
275 275
 			return err
276 276
 		}
... ...
@@ -317,7 +316,7 @@ func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
317 317
 		cli.negotiateLock.Lock()
318 318
 		defer cli.negotiateLock.Unlock()
319 319
 
320
-		ping, err := cli.Ping(ctx)
320
+		ping, err := cli.Ping(ctx, PingOptions{})
321 321
 		if err != nil {
322 322
 			// FIXME(thaJeztah): Ping returns an error when failing to connect to the API; we should not swallow the error here, and instead returning it.
323 323
 			return
... ...
@@ -338,7 +337,8 @@ func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
338 338
 //
339 339
 // If the API server's ping response does not contain an API version, it falls
340 340
 // back to the oldest API version supported.
341
-func (cli *Client) NegotiateAPIVersionPing(pingResponse types.Ping) {
341
+func (cli *Client) NegotiateAPIVersionPing(pingResponse PingResult) {
342
+	// TODO(thaJeztah): should this take a "Ping" option? It only consumes the version. This method should be removed overall and not be exported.
342 343
 	if !cli.manualOverride {
343 344
 		// Avoid concurrent modification of version-related fields
344 345
 		cli.negotiateLock.Lock()
... ...
@@ -35,7 +35,7 @@ type stableAPIClient interface {
35 35
 	DaemonHost() string
36 36
 	ServerVersion(ctx context.Context) (types.Version, error)
37 37
 	NegotiateAPIVersion(ctx context.Context)
38
-	NegotiateAPIVersionPing(types.Ping)
38
+	NegotiateAPIVersionPing(PingResult)
39 39
 	HijackDialer
40 40
 	Dialer() func(context.Context) (net.Conn, error)
41 41
 	Close() error
... ...
@@ -187,7 +187,7 @@ type SystemAPIClient interface {
187 187
 	Info(ctx context.Context) (system.Info, error)
188 188
 	RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error)
189 189
 	DiskUsage(ctx context.Context, options DiskUsageOptions) (system.DiskUsage, error)
190
-	Ping(ctx context.Context) (types.Ping, error)
190
+	Ping(ctx context.Context, options PingOptions) (PingResult, error)
191 191
 }
192 192
 
193 193
 // VolumeAPIClient defines API client methods for the volumes
... ...
@@ -11,7 +11,6 @@ import (
11 11
 	"strings"
12 12
 	"testing"
13 13
 
14
-	"github.com/moby/moby/api/types"
15 14
 	"gotest.tools/v3/assert"
16 15
 	is "gotest.tools/v3/assert/cmp"
17 16
 	"gotest.tools/v3/skip"
... ...
@@ -267,7 +266,7 @@ func TestNegotiateAPIVersionEmpty(t *testing.T) {
267 267
 	const expected = fallbackAPIVersion
268 268
 
269 269
 	// test downgrade
270
-	client.NegotiateAPIVersionPing(types.Ping{})
270
+	client.NegotiateAPIVersionPing(PingResult{})
271 271
 	assert.Check(t, is.Equal(client.ClientVersion(), expected))
272 272
 }
273 273
 
... ...
@@ -330,7 +329,7 @@ func TestNegotiateAPIVersion(t *testing.T) {
330 330
 			}
331 331
 			client, err := NewClientWithOpts(opts...)
332 332
 			assert.NilError(t, err)
333
-			client.NegotiateAPIVersionPing(types.Ping{APIVersion: tc.pingVersion})
333
+			client.NegotiateAPIVersionPing(PingResult{APIVersion: tc.pingVersion})
334 334
 			assert.Check(t, is.Equal(tc.expectedVersion, client.ClientVersion()))
335 335
 		})
336 336
 	}
... ...
@@ -346,7 +345,7 @@ func TestNegotiateAPIVersionOverride(t *testing.T) {
346 346
 	assert.NilError(t, err)
347 347
 
348 348
 	// test that we honored the env var
349
-	client.NegotiateAPIVersionPing(types.Ping{APIVersion: "1.24"})
349
+	client.NegotiateAPIVersionPing(PingResult{APIVersion: "1.24"})
350 350
 	assert.Check(t, is.Equal(client.ClientVersion(), expected))
351 351
 }
352 352
 
... ...
@@ -404,7 +403,7 @@ func TestNegotiateAPIVersionWithEmptyVersion(t *testing.T) {
404 404
 	assert.NilError(t, err)
405 405
 
406 406
 	const expected = "1.50"
407
-	client.NegotiateAPIVersionPing(types.Ping{APIVersion: expected})
407
+	client.NegotiateAPIVersionPing(PingResult{APIVersion: expected})
408 408
 	assert.Check(t, is.Equal(client.ClientVersion(), expected))
409 409
 }
410 410
 
... ...
@@ -415,7 +414,7 @@ func TestNegotiateAPIVersionWithFixedVersion(t *testing.T) {
415 415
 	client, err := NewClientWithOpts(WithVersion(customVersion))
416 416
 	assert.NilError(t, err)
417 417
 
418
-	client.NegotiateAPIVersionPing(types.Ping{APIVersion: "1.49"})
418
+	client.NegotiateAPIVersionPing(PingResult{APIVersion: "1.49"})
419 419
 	assert.Check(t, is.Equal(client.ClientVersion(), customVersion))
420 420
 }
421 421
 
... ...
@@ -1,7 +1,6 @@
1 1
 package client
2 2
 
3 3
 import (
4
-	"context"
5 4
 	"net/http"
6 5
 	"runtime"
7 6
 	"testing"
... ...
@@ -72,7 +71,7 @@ func TestWithUserAgent(t *testing.T) {
72 72
 			}),
73 73
 		)
74 74
 		assert.NilError(t, err)
75
-		_, err = c.Ping(context.Background())
75
+		_, err = c.Ping(t.Context(), PingOptions{})
76 76
 		assert.NilError(t, err)
77 77
 		assert.NilError(t, c.Close())
78 78
 	})
... ...
@@ -87,7 +86,7 @@ func TestWithUserAgent(t *testing.T) {
87 87
 			}),
88 88
 		)
89 89
 		assert.NilError(t, err)
90
-		_, err = c.Ping(context.Background())
90
+		_, err = c.Ping(t.Context(), PingOptions{})
91 91
 		assert.NilError(t, err)
92 92
 		assert.NilError(t, c.Close())
93 93
 	})
... ...
@@ -101,7 +100,7 @@ func TestWithUserAgent(t *testing.T) {
101 101
 			}),
102 102
 		)
103 103
 		assert.NilError(t, err)
104
-		_, err = c.Ping(context.Background())
104
+		_, err = c.Ping(t.Context(), PingOptions{})
105 105
 		assert.NilError(t, err)
106 106
 		assert.NilError(t, c.Close())
107 107
 	})
... ...
@@ -115,7 +114,7 @@ func TestWithUserAgent(t *testing.T) {
115 115
 			}),
116 116
 		)
117 117
 		assert.NilError(t, err)
118
-		_, err = c.Ping(context.Background())
118
+		_, err = c.Ping(t.Context(), PingOptions{})
119 119
 		assert.NilError(t, err)
120 120
 		assert.NilError(t, c.Close())
121 121
 	})
... ...
@@ -130,7 +129,7 @@ func TestWithUserAgent(t *testing.T) {
130 130
 			}),
131 131
 		)
132 132
 		assert.NilError(t, err)
133
-		_, err = c.Ping(context.Background())
133
+		_, err = c.Ping(t.Context(), PingOptions{})
134 134
 		assert.NilError(t, err)
135 135
 		assert.NilError(t, c.Close())
136 136
 	})
... ...
@@ -6,11 +6,42 @@ import (
6 6
 	"path"
7 7
 	"strings"
8 8
 
9
-	"github.com/moby/moby/api/types"
10 9
 	"github.com/moby/moby/api/types/build"
11 10
 	"github.com/moby/moby/api/types/swarm"
12 11
 )
13 12
 
13
+// PingOptions holds options for [client.Ping].
14
+type PingOptions struct {
15
+	// Add future optional parameters here
16
+}
17
+
18
+// PingResult holds the result of a [Client.Ping] API call.
19
+type PingResult struct {
20
+	APIVersion     string
21
+	OSType         string
22
+	Experimental   bool
23
+	BuilderVersion build.BuilderVersion
24
+
25
+	// SwarmStatus provides information about the current swarm status of the
26
+	// engine, obtained from the "Swarm" header in the API response.
27
+	//
28
+	// It can be a nil struct if the API version does not provide this header
29
+	// in the ping response, or if an error occurred, in which case the client
30
+	// should use other ways to get the current swarm status, such as the /swarm
31
+	// endpoint.
32
+	SwarmStatus *SwarmStatus
33
+}
34
+
35
+// SwarmStatus provides information about the current swarm status and role,
36
+// obtained from the "Swarm" header in the API response.
37
+type SwarmStatus struct {
38
+	// NodeState represents the state of the node.
39
+	NodeState swarm.LocalNodeState
40
+
41
+	// ControlAvailable indicates if the node is a swarm manager.
42
+	ControlAvailable bool
43
+}
44
+
14 45
 // Ping pings the server and returns the value of the "Docker-Experimental",
15 46
 // "Builder-Version", "OS-Type" & "API-Version" headers. It attempts to use
16 47
 // a HEAD request on the endpoint, but falls back to GET if HEAD is not supported
... ...
@@ -18,13 +49,13 @@ import (
18 18
 // may be returned if the daemon is in an unhealthy state, but returns errors
19 19
 // for other non-success status codes, failing to connect to the API, or failing
20 20
 // to parse the API response.
21
-func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
21
+func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, error) {
22 22
 	// Using cli.buildRequest() + cli.doRequest() instead of cli.sendRequest()
23 23
 	// because ping requests are used during API version negotiation, so we want
24 24
 	// to hit the non-versioned /_ping endpoint, not /v1.xx/_ping
25 25
 	req, err := cli.buildRequest(ctx, http.MethodHead, path.Join(cli.basePath, "/_ping"), nil, nil)
26 26
 	if err != nil {
27
-		return types.Ping{}, err
27
+		return PingResult{}, err
28 28
 	}
29 29
 	resp, err := cli.doRequest(req)
30 30
 	defer ensureReaderClosed(resp)
... ...
@@ -33,7 +64,7 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
33 33
 		// we got a "OK" (200) status. For non-200 status-codes, we fall
34 34
 		// back to doing a GET request, as a HEAD request won't have a
35 35
 		// response-body to get error details from.
36
-		return newPingResponse(resp), nil
36
+		return newPingResult(resp), nil
37 37
 	}
38 38
 
39 39
 	// 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) {
42 42
 	defer ensureReaderClosed(resp)
43 43
 	if err != nil {
44 44
 		// Failed to connect.
45
-		return types.Ping{}, err
45
+		return PingResult{}, err
46 46
 	}
47 47
 
48 48
 	// GET request succeeded but may have returned a non-200 status.
49 49
 	// Return a Ping response, together with any error returned by
50 50
 	// the API server.
51
-	return newPingResponse(resp), checkResponseErr(resp)
51
+	return newPingResult(resp), checkResponseErr(resp)
52 52
 }
53 53
 
54
-func newPingResponse(resp *http.Response) types.Ping {
54
+func newPingResult(resp *http.Response) PingResult {
55 55
 	if resp == nil {
56
-		return types.Ping{}
56
+		return PingResult{}
57 57
 	}
58
-	var swarmStatus *swarm.Status
58
+	var swarmStatus *SwarmStatus
59 59
 	if si := resp.Header.Get("Swarm"); si != "" {
60 60
 		state, role, _ := strings.Cut(si, "/")
61
-		swarmStatus = &swarm.Status{
61
+		swarmStatus = &SwarmStatus{
62 62
 			NodeState:        swarm.LocalNodeState(state),
63 63
 			ControlAvailable: role == "manager",
64 64
 		}
65 65
 	}
66 66
 
67
-	return types.Ping{
67
+	return PingResult{
68 68
 		APIVersion:     resp.Header.Get("Api-Version"),
69 69
 		OSType:         resp.Header.Get("Ostype"),
70 70
 		Experimental:   resp.Header.Get("Docker-Experimental") == "true",
... ...
@@ -1,7 +1,6 @@
1 1
 package client
2 2
 
3 3
 import (
4
-	"context"
5 4
 	"errors"
6 5
 	"fmt"
7 6
 	"io"
... ...
@@ -10,7 +9,6 @@ import (
10 10
 	"strings"
11 11
 	"testing"
12 12
 
13
-	"github.com/moby/moby/api/types/swarm"
14 13
 	"gotest.tools/v3/assert"
15 14
 	is "gotest.tools/v3/assert/cmp"
16 15
 )
... ...
@@ -34,19 +32,19 @@ func TestPingFail(t *testing.T) {
34 34
 	}))
35 35
 	assert.NilError(t, err)
36 36
 
37
-	ping, err := client.Ping(context.Background())
37
+	ping, err := client.Ping(t.Context(), PingOptions{})
38 38
 	assert.Check(t, is.ErrorContains(err, "some error with the server"))
39 39
 	assert.Check(t, is.Equal(false, ping.Experimental))
40 40
 	assert.Check(t, is.Equal("", ping.APIVersion))
41
-	var si *swarm.Status
41
+	var si *SwarmStatus
42 42
 	assert.Check(t, is.Equal(si, ping.SwarmStatus))
43 43
 
44 44
 	withHeader = true
45
-	ping2, err := client.Ping(context.Background())
45
+	ping2, err := client.Ping(t.Context(), PingOptions{})
46 46
 	assert.Check(t, is.ErrorContains(err, "some error with the server"))
47 47
 	assert.Check(t, is.Equal(true, ping2.Experimental))
48 48
 	assert.Check(t, is.Equal("awesome", ping2.APIVersion))
49
-	assert.Check(t, is.Equal(swarm.Status{NodeState: "inactive"}, *ping2.SwarmStatus))
49
+	assert.Check(t, is.Equal(SwarmStatus{NodeState: "inactive"}, *ping2.SwarmStatus))
50 50
 }
51 51
 
52 52
 // TestPingWithError tests the case where there is a protocol error in the ping.
... ...
@@ -57,11 +55,11 @@ func TestPingWithError(t *testing.T) {
57 57
 	}))
58 58
 	assert.NilError(t, err)
59 59
 
60
-	ping, err := client.Ping(context.Background())
60
+	ping, err := client.Ping(t.Context(), PingOptions{})
61 61
 	assert.Check(t, is.ErrorContains(err, "some connection error"))
62 62
 	assert.Check(t, is.Equal(false, ping.Experimental))
63 63
 	assert.Check(t, is.Equal("", ping.APIVersion))
64
-	var si *swarm.Status
64
+	var si *SwarmStatus
65 65
 	assert.Check(t, is.Equal(si, ping.SwarmStatus))
66 66
 }
67 67
 
... ...
@@ -78,11 +76,11 @@ func TestPingSuccess(t *testing.T) {
78 78
 		return resp, nil
79 79
 	}))
80 80
 	assert.NilError(t, err)
81
-	ping, err := client.Ping(context.Background())
81
+	ping, err := client.Ping(t.Context(), PingOptions{})
82 82
 	assert.NilError(t, err)
83 83
 	assert.Check(t, is.Equal(true, ping.Experimental))
84 84
 	assert.Check(t, is.Equal("awesome", ping.APIVersion))
85
-	assert.Check(t, is.Equal(swarm.Status{NodeState: "active", ControlAvailable: true}, *ping.SwarmStatus))
85
+	assert.Check(t, is.Equal(SwarmStatus{NodeState: "active", ControlAvailable: true}, *ping.SwarmStatus))
86 86
 }
87 87
 
88 88
 // TestPingHeadFallback tests that the client falls back to GET if HEAD fails.
... ...
@@ -131,7 +129,7 @@ func TestPingHeadFallback(t *testing.T) {
131 131
 				return resp, nil
132 132
 			}))
133 133
 			assert.NilError(t, err)
134
-			ping, _ := client.Ping(context.Background())
134
+			ping, _ := client.Ping(t.Context(), PingOptions{})
135 135
 			assert.Check(t, is.Equal(ping.APIVersion, "1.2.3"))
136 136
 			assert.Check(t, is.DeepEqual(reqs, tc.expected))
137 137
 		})
... ...
@@ -210,7 +210,7 @@ func TestResponseErrors(t *testing.T) {
210 210
 				client, err = NewClientWithOpts(WithHTTPClient(client.client), WithVersion(tc.apiVersion))
211 211
 			}
212 212
 			assert.NilError(t, err)
213
-			_, err = client.Ping(context.Background())
213
+			_, err = client.Ping(t.Context(), PingOptions{})
214 214
 			assert.Check(t, is.Error(err, tc.expected))
215 215
 			assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
216 216
 		})
... ...
@@ -229,7 +229,7 @@ func TestInfiniteError(t *testing.T) {
229 229
 	}))
230 230
 	assert.NilError(t, err)
231 231
 
232
-	_, err = client.Ping(context.Background())
232
+	_, err = client.Ping(t.Context(), PingOptions{})
233 233
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
234 234
 	assert.Check(t, is.ErrorContains(err, "request returned Internal Server Error"))
235 235
 }
... ...
@@ -2235,7 +2235,7 @@ func (s *DockerDaemonSuite) TestFailedPluginRemove(c *testing.T) {
2235 2235
 	d.Restart(c)
2236 2236
 	ctx, cancel = context.WithTimeout(testutil.GetContext(c), 30*time.Second)
2237 2237
 	defer cancel()
2238
-	_, err = apiClient.Ping(ctx)
2238
+	_, err = apiClient.Ping(ctx, client.PingOptions{})
2239 2239
 	assert.NilError(c, err)
2240 2240
 
2241 2241
 	_, err = apiClient.PluginInspect(ctx, name, client.PluginInspectOptions{})
... ...
@@ -68,7 +68,7 @@ func TestPingSwarmHeader(t *testing.T) {
68 68
 
69 69
 	t.Run("before swarm init", func(t *testing.T) {
70 70
 		ctx := testutil.StartSpan(ctx, t)
71
-		p, err := apiClient.Ping(ctx)
71
+		p, err := apiClient.Ping(ctx, client.PingOptions{})
72 72
 		assert.NilError(t, err)
73 73
 		assert.Equal(t, p.SwarmStatus.NodeState, swarm.LocalNodeStateInactive)
74 74
 		assert.Equal(t, p.SwarmStatus.ControlAvailable, false)
... ...
@@ -79,7 +79,7 @@ func TestPingSwarmHeader(t *testing.T) {
79 79
 
80 80
 	t.Run("after swarm init", func(t *testing.T) {
81 81
 		ctx := testutil.StartSpan(ctx, t)
82
-		p, err := apiClient.Ping(ctx)
82
+		p, err := apiClient.Ping(ctx, client.PingOptions{})
83 83
 		assert.NilError(t, err)
84 84
 		assert.Equal(t, p.SwarmStatus.NodeState, swarm.LocalNodeStateActive)
85 85
 		assert.Equal(t, p.SwarmStatus.ControlAvailable, true)
... ...
@@ -90,7 +90,7 @@ func TestPingSwarmHeader(t *testing.T) {
90 90
 
91 91
 	t.Run("after swarm leave", func(t *testing.T) {
92 92
 		ctx := testutil.StartSpan(ctx, t)
93
-		p, err := apiClient.Ping(ctx)
93
+		p, err := apiClient.Ping(ctx, client.PingOptions{})
94 94
 		assert.NilError(t, err)
95 95
 		assert.Equal(t, p.SwarmStatus.NodeState, swarm.LocalNodeStateInactive)
96 96
 		assert.Equal(t, p.SwarmStatus.ControlAvailable, false)
... ...
@@ -116,7 +116,7 @@ func TestPingBuilderHeader(t *testing.T) {
116 116
 			expected = build.BuilderV1
117 117
 		}
118 118
 
119
-		p, err := apiClient.Ping(ctx)
119
+		p, err := apiClient.Ping(ctx, client.PingOptions{})
120 120
 		assert.NilError(t, err)
121 121
 		assert.Equal(t, p.BuilderVersion, expected)
122 122
 	})
... ...
@@ -130,7 +130,7 @@ func TestPingBuilderHeader(t *testing.T) {
130 130
 		defer d.Stop(t)
131 131
 
132 132
 		expected := build.BuilderV1
133
-		p, err := apiClient.Ping(ctx)
133
+		p, err := apiClient.Ping(ctx, client.PingOptions{})
134 134
 		assert.NilError(t, err)
135 135
 		assert.Equal(t, p.BuilderVersion, expected)
136 136
 	})
... ...
@@ -214,16 +214,6 @@ type Info struct {
214 214
 	Warnings []string `json:",omitempty"`
215 215
 }
216 216
 
217
-// Status provides information about the current swarm status and role,
218
-// obtained from the "Swarm" header in the API response.
219
-type Status struct {
220
-	// NodeState represents the state of the node.
221
-	NodeState LocalNodeState
222
-
223
-	// ControlAvailable indicates if the node is a swarm manager.
224
-	ControlAvailable bool
225
-}
226
-
227 217
 // Peer represents a peer.
228 218
 type Peer struct {
229 219
 	NodeID string
... ...
@@ -1,10 +1,5 @@
1 1
 package types
2 2
 
3
-import (
4
-	"github.com/moby/moby/api/types/build"
5
-	"github.com/moby/moby/api/types/swarm"
6
-)
7
-
8 3
 const (
9 4
 	// MediaTypeRawStream is vendor specific MIME-Type set for raw TTY streams
10 5
 	MediaTypeRawStream = "application/vnd.docker.raw-stream"
... ...
@@ -22,24 +17,6 @@ const (
22 22
 	MediaTypeJSONSequence = "application/json-seq"
23 23
 )
24 24
 
25
-// Ping contains response of Engine API:
26
-// GET "/_ping"
27
-type Ping struct {
28
-	APIVersion     string
29
-	OSType         string
30
-	Experimental   bool
31
-	BuilderVersion build.BuilderVersion
32
-
33
-	// SwarmStatus provides information about the current swarm status of the
34
-	// engine, obtained from the "Swarm" header in the API response.
35
-	//
36
-	// It can be a nil struct if the API version does not provide this header
37
-	// in the ping response, or if an error occurred, in which case the client
38
-	// should use other ways to get the current swarm status, such as the /swarm
39
-	// endpoint.
40
-	SwarmStatus *swarm.Status
41
-}
42
-
43 25
 // ComponentVersion describes the version information for a specific component.
44 26
 type ComponentVersion struct {
45 27
 	Name    string
... ...
@@ -56,7 +56,6 @@ import (
56 56
 
57 57
 	cerrdefs "github.com/containerd/errdefs"
58 58
 	"github.com/docker/go-connections/sockets"
59
-	"github.com/moby/moby/api/types"
60 59
 	"github.com/moby/moby/api/types/versions"
61 60
 	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
62 61
 )
... ...
@@ -270,7 +269,7 @@ func (cli *Client) checkVersion(ctx context.Context) error {
270 270
 			return nil
271 271
 		}
272 272
 
273
-		ping, err := cli.Ping(ctx)
273
+		ping, err := cli.Ping(ctx, PingOptions{})
274 274
 		if err != nil {
275 275
 			return err
276 276
 		}
... ...
@@ -317,7 +316,7 @@ func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
317 317
 		cli.negotiateLock.Lock()
318 318
 		defer cli.negotiateLock.Unlock()
319 319
 
320
-		ping, err := cli.Ping(ctx)
320
+		ping, err := cli.Ping(ctx, PingOptions{})
321 321
 		if err != nil {
322 322
 			// FIXME(thaJeztah): Ping returns an error when failing to connect to the API; we should not swallow the error here, and instead returning it.
323 323
 			return
... ...
@@ -338,7 +337,8 @@ func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
338 338
 //
339 339
 // If the API server's ping response does not contain an API version, it falls
340 340
 // back to the oldest API version supported.
341
-func (cli *Client) NegotiateAPIVersionPing(pingResponse types.Ping) {
341
+func (cli *Client) NegotiateAPIVersionPing(pingResponse PingResult) {
342
+	// TODO(thaJeztah): should this take a "Ping" option? It only consumes the version. This method should be removed overall and not be exported.
342 343
 	if !cli.manualOverride {
343 344
 		// Avoid concurrent modification of version-related fields
344 345
 		cli.negotiateLock.Lock()
... ...
@@ -35,7 +35,7 @@ type stableAPIClient interface {
35 35
 	DaemonHost() string
36 36
 	ServerVersion(ctx context.Context) (types.Version, error)
37 37
 	NegotiateAPIVersion(ctx context.Context)
38
-	NegotiateAPIVersionPing(types.Ping)
38
+	NegotiateAPIVersionPing(PingResult)
39 39
 	HijackDialer
40 40
 	Dialer() func(context.Context) (net.Conn, error)
41 41
 	Close() error
... ...
@@ -187,7 +187,7 @@ type SystemAPIClient interface {
187 187
 	Info(ctx context.Context) (system.Info, error)
188 188
 	RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error)
189 189
 	DiskUsage(ctx context.Context, options DiskUsageOptions) (system.DiskUsage, error)
190
-	Ping(ctx context.Context) (types.Ping, error)
190
+	Ping(ctx context.Context, options PingOptions) (PingResult, error)
191 191
 }
192 192
 
193 193
 // VolumeAPIClient defines API client methods for the volumes
... ...
@@ -6,11 +6,42 @@ import (
6 6
 	"path"
7 7
 	"strings"
8 8
 
9
-	"github.com/moby/moby/api/types"
10 9
 	"github.com/moby/moby/api/types/build"
11 10
 	"github.com/moby/moby/api/types/swarm"
12 11
 )
13 12
 
13
+// PingOptions holds options for [client.Ping].
14
+type PingOptions struct {
15
+	// Add future optional parameters here
16
+}
17
+
18
+// PingResult holds the result of a [Client.Ping] API call.
19
+type PingResult struct {
20
+	APIVersion     string
21
+	OSType         string
22
+	Experimental   bool
23
+	BuilderVersion build.BuilderVersion
24
+
25
+	// SwarmStatus provides information about the current swarm status of the
26
+	// engine, obtained from the "Swarm" header in the API response.
27
+	//
28
+	// It can be a nil struct if the API version does not provide this header
29
+	// in the ping response, or if an error occurred, in which case the client
30
+	// should use other ways to get the current swarm status, such as the /swarm
31
+	// endpoint.
32
+	SwarmStatus *SwarmStatus
33
+}
34
+
35
+// SwarmStatus provides information about the current swarm status and role,
36
+// obtained from the "Swarm" header in the API response.
37
+type SwarmStatus struct {
38
+	// NodeState represents the state of the node.
39
+	NodeState swarm.LocalNodeState
40
+
41
+	// ControlAvailable indicates if the node is a swarm manager.
42
+	ControlAvailable bool
43
+}
44
+
14 45
 // Ping pings the server and returns the value of the "Docker-Experimental",
15 46
 // "Builder-Version", "OS-Type" & "API-Version" headers. It attempts to use
16 47
 // a HEAD request on the endpoint, but falls back to GET if HEAD is not supported
... ...
@@ -18,13 +49,13 @@ import (
18 18
 // may be returned if the daemon is in an unhealthy state, but returns errors
19 19
 // for other non-success status codes, failing to connect to the API, or failing
20 20
 // to parse the API response.
21
-func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
21
+func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, error) {
22 22
 	// Using cli.buildRequest() + cli.doRequest() instead of cli.sendRequest()
23 23
 	// because ping requests are used during API version negotiation, so we want
24 24
 	// to hit the non-versioned /_ping endpoint, not /v1.xx/_ping
25 25
 	req, err := cli.buildRequest(ctx, http.MethodHead, path.Join(cli.basePath, "/_ping"), nil, nil)
26 26
 	if err != nil {
27
-		return types.Ping{}, err
27
+		return PingResult{}, err
28 28
 	}
29 29
 	resp, err := cli.doRequest(req)
30 30
 	defer ensureReaderClosed(resp)
... ...
@@ -33,7 +64,7 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
33 33
 		// we got a "OK" (200) status. For non-200 status-codes, we fall
34 34
 		// back to doing a GET request, as a HEAD request won't have a
35 35
 		// response-body to get error details from.
36
-		return newPingResponse(resp), nil
36
+		return newPingResult(resp), nil
37 37
 	}
38 38
 
39 39
 	// 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) {
42 42
 	defer ensureReaderClosed(resp)
43 43
 	if err != nil {
44 44
 		// Failed to connect.
45
-		return types.Ping{}, err
45
+		return PingResult{}, err
46 46
 	}
47 47
 
48 48
 	// GET request succeeded but may have returned a non-200 status.
49 49
 	// Return a Ping response, together with any error returned by
50 50
 	// the API server.
51
-	return newPingResponse(resp), checkResponseErr(resp)
51
+	return newPingResult(resp), checkResponseErr(resp)
52 52
 }
53 53
 
54
-func newPingResponse(resp *http.Response) types.Ping {
54
+func newPingResult(resp *http.Response) PingResult {
55 55
 	if resp == nil {
56
-		return types.Ping{}
56
+		return PingResult{}
57 57
 	}
58
-	var swarmStatus *swarm.Status
58
+	var swarmStatus *SwarmStatus
59 59
 	if si := resp.Header.Get("Swarm"); si != "" {
60 60
 		state, role, _ := strings.Cut(si, "/")
61
-		swarmStatus = &swarm.Status{
61
+		swarmStatus = &SwarmStatus{
62 62
 			NodeState:        swarm.LocalNodeState(state),
63 63
 			ControlAvailable: role == "manager",
64 64
 		}
65 65
 	}
66 66
 
67
-	return types.Ping{
67
+	return PingResult{
68 68
 		APIVersion:     resp.Header.Get("Api-Version"),
69 69
 		OSType:         resp.Header.Get("Ostype"),
70 70
 		Experimental:   resp.Header.Get("Docker-Experimental") == "true",