1
- /*
2
- Package client is a Go client for the Docker Engine API.
3
-
4
- For more information about the Engine API, see the documentation:
5
- https://docs.docker.com/reference/api/engine/
6
-
7
- # Usage
8
-
9
- You use the library by constructing a client object using [NewClientWithOpts]
10
- and calling methods on it. The client can be configured from environment
11
- variables by passing the [FromEnv] option, or configured manually by passing any
12
- of the other available [Opts].
13
-
14
- For example, to list running containers (the equivalent of "docker ps"):
15
-
16
- package main
17
-
18
- import (
19
- "context"
20
- "fmt"
21
-
22
- "github.com/docker/docker/api/types/container"
23
- "github.com/docker/docker/client"
24
- )
25
-
26
- func main() {
27
- cli, err := client.NewClientWithOpts(client.FromEnv)
28
- if err != nil {
29
- panic(err)
30
- }
31
-
32
- containers, err := cli.ContainerList(context.Background(), container.ListOptions{})
33
- if err != nil {
34
- panic(err)
35
- }
36
-
37
- for _, ctr := range containers {
38
- fmt.Printf("%s %s\n", ctr.ID, ctr.Image)
39
- }
40
- }
41
- */
42
- package client // import "github.com/docker/docker/client"
1
+ package client
43
2
44
3
import (
45
4
"context"
46
5
"crypto/tls"
47
6
"net"
48
7
"net/http"
49
8
"net/url"
50
- "path"
51
9
"strings"
52
- "sync"
53
- "sync/atomic"
54
10
"time"
55
11
56
- "github.com/docker/docker/api"
57
- "github.com/docker/docker/api/types"
58
- "github.com/docker/docker/api/types/versions"
59
12
"github.com/docker/go-connections/sockets"
60
13
"github.com/pkg/errors"
61
- "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
62
- "go.opentelemetry.io/otel/trace"
63
14
)
64
15
65
16
// DummyHost is a hostname used for local communication.
@@ -92,12 +43,8 @@ import (
92
43
// [Go stdlib]: https://github.com/golang/go/blob/6244b1946bc2101b01955468f1be502dbadd6807/src/net/http/transport.go#L558-L569
93
44
const DummyHost = "api.moby.localhost"
94
45
95
- // fallbackAPIVersion is the version to fallback to if API-version negotiation
96
- // fails. This version is the highest version of the API before API-version
97
- // negotiation was introduced. If negotiation fails (or no API version was
98
- // included in the API response), we assume the API server uses the most
99
- // recent version before negotiation was introduced.
100
- const fallbackAPIVersion = "1.24"
46
+ // DefaultVersion is the pinned version of the docker API we utilize.
47
+ const DefaultVersion = "1.47"
101
48
102
49
// Client is the API client that performs all operations
103
50
// against a docker server.
@@ -116,29 +63,6 @@ type Client struct {
116
63
client * http.Client
117
64
// version of the server to talk to.
118
65
version string
119
- // userAgent is the User-Agent header to use for HTTP requests. It takes
120
- // precedence over User-Agent headers set in customHTTPHeaders, and other
121
- // header variables. When set to an empty string, the User-Agent header
122
- // is removed, and no header is sent.
123
- userAgent * string
124
- // custom HTTP headers configured by users.
125
- customHTTPHeaders map [string ]string
126
- // manualOverride is set to true when the version was set by users.
127
- manualOverride bool
128
-
129
- // negotiateVersion indicates if the client should automatically negotiate
130
- // the API version to use when making requests. API version negotiation is
131
- // performed on the first request, after which negotiated is set to "true"
132
- // so that subsequent requests do not re-negotiate.
133
- negotiateVersion bool
134
-
135
- // negotiated indicates that API version negotiation took place
136
- negotiated atomic.Bool
137
-
138
- // negotiateLock is used to single-flight the version negotiation process
139
- negotiateLock sync.Mutex
140
-
141
- tp trace.TracerProvider
142
66
143
67
// When the client transport is an *http.Transport (default) we need to do some extra things (like closing idle connections).
144
68
// Store the original transport as the http.Client transport will be wrapped with tracing libs.
@@ -196,7 +120,7 @@ func NewClientWithOpts(ops ...Opt) (*Client, error) {
196
120
}
197
121
c := & Client {
198
122
host : DefaultDockerHost ,
199
- version : api . DefaultVersion ,
123
+ version : DefaultVersion ,
200
124
client : client ,
201
125
proto : hostURL .Scheme ,
202
126
addr : hostURL .Host ,
@@ -226,15 +150,6 @@ func NewClientWithOpts(ops ...Opt) (*Client, error) {
226
150
c .scheme = "http"
227
151
}
228
152
}
229
-
230
- c .client .Transport = otelhttp .NewTransport (
231
- c .client .Transport ,
232
- otelhttp .WithTracerProvider (c .tp ),
233
- otelhttp .WithSpanNameFormatter (func (_ string , req * http.Request ) string {
234
- return req .Method + " " + req .URL .Path
235
- }),
236
- )
237
-
238
153
return c , nil
239
154
}
240
155
@@ -246,17 +161,17 @@ func (cli *Client) tlsConfig() *tls.Config {
246
161
}
247
162
248
163
func defaultHTTPClient (hostURL * url.URL ) (* http.Client , error ) {
249
- transport := & http.Transport {}
250
164
// Necessary to prevent long-lived processes using the
251
165
// client from leaking connections due to idle connections
252
166
// not being released.
253
167
// TODO: see if we can also address this from the server side,
254
168
// or in go-connections.
255
169
// see: https://github.com/moby/moby/issues/45539
256
- transport .MaxIdleConns = 6
257
- transport .IdleConnTimeout = 30 * time .Second
258
- err := sockets .ConfigureTransport (transport , hostURL .Scheme , hostURL .Host )
259
- if err != nil {
170
+ transport := & http.Transport {
171
+ MaxIdleConns : 6 ,
172
+ IdleConnTimeout : 30 * time .Second ,
173
+ }
174
+ if err := sockets .ConfigureTransport (transport , hostURL .Scheme , hostURL .Host ); err != nil {
260
175
return nil , err
261
176
}
262
177
return & http.Client {
@@ -274,138 +189,6 @@ func (cli *Client) Close() error {
274
189
return nil
275
190
}
276
191
277
- // checkVersion manually triggers API version negotiation (if configured).
278
- // This allows for version-dependent code to use the same version as will
279
- // be negotiated when making the actual requests, and for which cases
280
- // we cannot do the negotiation lazily.
281
- func (cli * Client ) checkVersion (ctx context.Context ) error {
282
- if ! cli .manualOverride && cli .negotiateVersion && ! cli .negotiated .Load () {
283
- // Ensure exclusive write access to version and negotiated fields
284
- cli .negotiateLock .Lock ()
285
- defer cli .negotiateLock .Unlock ()
286
-
287
- // May have been set during last execution of critical zone
288
- if cli .negotiated .Load () {
289
- return nil
290
- }
291
-
292
- ping , err := cli .Ping (ctx )
293
- if err != nil {
294
- return err
295
- }
296
- cli .negotiateAPIVersionPing (ping )
297
- }
298
- return nil
299
- }
300
-
301
- // getAPIPath returns the versioned request path to call the API.
302
- // It appends the query parameters to the path if they are not empty.
303
- func (cli * Client ) getAPIPath (ctx context.Context , p string , query url.Values ) string {
304
- var apiPath string
305
- _ = cli .checkVersion (ctx )
306
- if cli .version != "" {
307
- v := strings .TrimPrefix (cli .version , "v" )
308
- apiPath = path .Join (cli .basePath , "/v" + v , p )
309
- } else {
310
- apiPath = path .Join (cli .basePath , p )
311
- }
312
- return (& url.URL {Path : apiPath , RawQuery : query .Encode ()}).String ()
313
- }
314
-
315
- // ClientVersion returns the API version used by this client.
316
- func (cli * Client ) ClientVersion () string {
317
- return cli .version
318
- }
319
-
320
- // NegotiateAPIVersion queries the API and updates the version to match the API
321
- // version. NegotiateAPIVersion downgrades the client's API version to match the
322
- // APIVersion if the ping version is lower than the default version. If the API
323
- // version reported by the server is higher than the maximum version supported
324
- // by the client, it uses the client's maximum version.
325
- //
326
- // If a manual override is in place, either through the "DOCKER_API_VERSION"
327
- // ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
328
- // with a fixed version ([WithVersion]), no negotiation is performed.
329
- //
330
- // If the API server's ping response does not contain an API version, or if the
331
- // client did not get a successful ping response, it assumes it is connected with
332
- // an old daemon that does not support API version negotiation, in which case it
333
- // downgrades to the latest version of the API before version negotiation was
334
- // added (1.24).
335
- func (cli * Client ) NegotiateAPIVersion (ctx context.Context ) {
336
- if ! cli .manualOverride {
337
- // Avoid concurrent modification of version-related fields
338
- cli .negotiateLock .Lock ()
339
- defer cli .negotiateLock .Unlock ()
340
-
341
- ping , err := cli .Ping (ctx )
342
- if err != nil {
343
- // FIXME(thaJeztah): Ping returns an error when failing to connect to the API; we should not swallow the error here, and instead returning it.
344
- return
345
- }
346
- cli .negotiateAPIVersionPing (ping )
347
- }
348
- }
349
-
350
- // NegotiateAPIVersionPing downgrades the client's API version to match the
351
- // APIVersion in the ping response. If the API version in pingResponse is higher
352
- // than the maximum version supported by the client, it uses the client's maximum
353
- // version.
354
- //
355
- // If a manual override is in place, either through the "DOCKER_API_VERSION"
356
- // ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
357
- // with a fixed version ([WithVersion]), no negotiation is performed.
358
- //
359
- // If the API server's ping response does not contain an API version, we assume
360
- // we are connected with an old daemon without API version negotiation support,
361
- // and downgrade to the latest version of the API before version negotiation was
362
- // added (1.24).
363
- func (cli * Client ) NegotiateAPIVersionPing (pingResponse types.Ping ) {
364
- if ! cli .manualOverride {
365
- // Avoid concurrent modification of version-related fields
366
- cli .negotiateLock .Lock ()
367
- defer cli .negotiateLock .Unlock ()
368
-
369
- cli .negotiateAPIVersionPing (pingResponse )
370
- }
371
- }
372
-
373
- // negotiateAPIVersionPing queries the API and updates the version to match the
374
- // API version from the ping response.
375
- func (cli * Client ) negotiateAPIVersionPing (pingResponse types.Ping ) {
376
- // default to the latest version before versioning headers existed
377
- if pingResponse .APIVersion == "" {
378
- pingResponse .APIVersion = fallbackAPIVersion
379
- }
380
-
381
- // if the client is not initialized with a version, start with the latest supported version
382
- if cli .version == "" {
383
- cli .version = api .DefaultVersion
384
- }
385
-
386
- // if server version is lower than the client version, downgrade
387
- if versions .LessThan (pingResponse .APIVersion , cli .version ) {
388
- cli .version = pingResponse .APIVersion
389
- }
390
-
391
- // Store the results, so that automatic API version negotiation (if enabled)
392
- // won't be performed on the next request.
393
- if cli .negotiateVersion {
394
- cli .negotiated .Store (true )
395
- }
396
- }
397
-
398
- // DaemonHost returns the host address used by the client
399
- func (cli * Client ) DaemonHost () string {
400
- return cli .host
401
- }
402
-
403
- // HTTPClient returns a copy of the HTTP client bound to the server
404
- func (cli * Client ) HTTPClient () * http.Client {
405
- c := * cli .client
406
- return & c
407
- }
408
-
409
192
// ParseHostURL parses a url string, validates the string is a host url, and
410
193
// returns the parsed URL
411
194
func ParseHostURL (host string ) (* url.URL , error ) {
0 commit comments