Browse code

Create github.com/moby/moby/client module

Signed-off-by: Derek McGowan <derek@mcg.dev>

Derek McGowan authored on 2025/07/22 01:30:26
Showing 200 changed files
... ...
@@ -20,7 +20,7 @@ For example, to list running containers (the equivalent of "docker ps"):
20 20
 		"fmt"
21 21
 
22 22
 		"github.com/moby/moby/api/types/container"
23
-		"github.com/docker/docker/client"
23
+		"github.com/moby/moby/client"
24 24
 	)
25 25
 
26 26
 	func main() {
27 27
new file mode 100644
... ...
@@ -0,0 +1,34 @@
0
+module github.com/moby/moby/client
1
+
2
+go 1.23.0
3
+
4
+require (
5
+	github.com/Microsoft/go-winio v0.6.2
6
+	github.com/containerd/errdefs v1.0.0
7
+	github.com/containerd/errdefs/pkg v0.3.0
8
+	github.com/distribution/reference v0.6.0
9
+	github.com/docker/go-connections v0.5.0
10
+	github.com/moby/moby/api v0.0.0
11
+	github.com/opencontainers/go-digest v1.0.0
12
+	github.com/opencontainers/image-spec v1.1.1
13
+	github.com/pkg/errors v0.9.1
14
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0
15
+	go.opentelemetry.io/otel/trace v1.35.0
16
+	gotest.tools/v3 v3.5.2
17
+)
18
+
19
+require (
20
+	github.com/docker/go-units v0.5.0 // indirect
21
+	github.com/felixge/httpsnoop v1.0.4 // indirect
22
+	github.com/go-logr/logr v1.4.2 // indirect
23
+	github.com/go-logr/stdr v1.2.2 // indirect
24
+	github.com/gogo/protobuf v1.3.2 // indirect
25
+	github.com/google/go-cmp v0.7.0 // indirect
26
+	github.com/moby/docker-image-spec v1.3.1 // indirect
27
+	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
28
+	go.opentelemetry.io/otel v1.35.0 // indirect
29
+	go.opentelemetry.io/otel/metric v1.35.0 // indirect
30
+	golang.org/x/sys v0.33.0 // indirect
31
+)
32
+
33
+replace github.com/moby/moby/api => ../api
0 34
new file mode 100644
... ...
@@ -0,0 +1,88 @@
0
+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
1
+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
2
+github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
3
+github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
4
+github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
5
+github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
6
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8
+github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
9
+github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
10
+github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
11
+github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
12
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
13
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
14
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
15
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
16
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
17
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
18
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
19
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
20
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
21
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
22
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
23
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
24
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
25
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
26
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
27
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
28
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
29
+github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
30
+github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
31
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
32
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
33
+github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
34
+github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
35
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
36
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
37
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
38
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
39
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
40
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
41
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
42
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
43
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
44
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
45
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
46
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
47
+go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
48
+go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
49
+go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
50
+go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
51
+go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
52
+go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
53
+go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
54
+go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
55
+go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
56
+go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
57
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
58
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
59
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
60
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
61
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
62
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
63
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
64
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
65
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
66
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
67
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
68
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
69
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
70
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
71
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
72
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
73
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
74
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
75
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
76
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
77
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
78
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
79
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
80
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
81
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
82
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
83
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
84
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
85
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
86
+gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
87
+gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"testing"
11 11
 	"time"
12 12
 
13
-	"github.com/docker/docker/client"
14 13
 	"github.com/docker/docker/integration-cli/cli"
15 14
 	"github.com/docker/docker/pkg/stdcopy"
16 15
 	"github.com/docker/docker/testutil"
... ...
@@ -18,6 +17,7 @@ import (
18 18
 	"github.com/docker/go-connections/sockets"
19 19
 	"github.com/moby/moby/api/types"
20 20
 	"github.com/moby/moby/api/types/container"
21
+	"github.com/moby/moby/client"
21 22
 	"github.com/pkg/errors"
22 23
 	"golang.org/x/net/websocket"
23 24
 	"gotest.tools/v3/assert"
... ...
@@ -18,7 +18,6 @@ import (
18 18
 	"time"
19 19
 
20 20
 	cerrdefs "github.com/containerd/errdefs"
21
-	"github.com/docker/docker/client"
22 21
 	dconfig "github.com/docker/docker/daemon/config"
23 22
 	"github.com/docker/docker/daemon/volume"
24 23
 	"github.com/docker/docker/integration-cli/cli"
... ...
@@ -30,6 +29,7 @@ import (
30 30
 	"github.com/moby/moby/api/types/container"
31 31
 	"github.com/moby/moby/api/types/mount"
32 32
 	"github.com/moby/moby/api/types/network"
33
+	"github.com/moby/moby/client"
33 34
 	"gotest.tools/v3/assert"
34 35
 	is "gotest.tools/v3/assert/cmp"
35 36
 	"gotest.tools/v3/poll"
... ...
@@ -12,12 +12,12 @@ import (
12 12
 	"testing"
13 13
 	"time"
14 14
 
15
-	"github.com/docker/docker/client"
16 15
 	"github.com/docker/docker/integration-cli/checker"
17 16
 	"github.com/docker/docker/integration-cli/cli"
18 17
 	"github.com/docker/docker/testutil"
19 18
 	"github.com/docker/docker/testutil/request"
20 19
 	"github.com/moby/moby/api/types/container"
20
+	"github.com/moby/moby/client"
21 21
 	"gotest.tools/v3/assert"
22 22
 	is "gotest.tools/v3/assert/cmp"
23 23
 	"gotest.tools/v3/poll"
... ...
@@ -6,12 +6,12 @@ import (
6 6
 	"strings"
7 7
 	"testing"
8 8
 
9
-	"github.com/docker/docker/client"
10 9
 	"github.com/docker/docker/integration-cli/cli"
11 10
 	"github.com/docker/docker/integration-cli/cli/build"
12 11
 	"github.com/docker/docker/testutil"
13 12
 	"github.com/docker/docker/testutil/request"
14 13
 	"github.com/moby/moby/api/types/image"
14
+	"github.com/moby/moby/client"
15 15
 	"gotest.tools/v3/assert"
16 16
 )
17 17
 
... ...
@@ -5,10 +5,10 @@ import (
5 5
 	"strings"
6 6
 	"testing"
7 7
 
8
-	"github.com/docker/docker/client"
9 8
 	"github.com/docker/docker/integration-cli/cli"
10 9
 	"github.com/docker/docker/testutil"
11 10
 	"github.com/moby/moby/api/types/container"
11
+	"github.com/moby/moby/client"
12 12
 	"gotest.tools/v3/assert"
13 13
 	is "gotest.tools/v3/assert/cmp"
14 14
 )
... ...
@@ -11,12 +11,12 @@ import (
11 11
 	"testing"
12 12
 	"time"
13 13
 
14
-	"github.com/docker/docker/client"
15 14
 	"github.com/docker/docker/integration-cli/cli"
16 15
 	"github.com/docker/docker/pkg/stdcopy"
17 16
 	"github.com/docker/docker/testutil"
18 17
 	"github.com/docker/docker/testutil/request"
19 18
 	"github.com/moby/moby/api/types/container"
19
+	"github.com/moby/moby/client"
20 20
 	"gotest.tools/v3/assert"
21 21
 )
22 22
 
... ...
@@ -11,12 +11,12 @@ import (
11 11
 	"testing"
12 12
 	"time"
13 13
 
14
-	"github.com/docker/docker/client"
15 14
 	"github.com/docker/docker/integration-cli/cli"
16 15
 	"github.com/docker/docker/testutil"
17 16
 	"github.com/docker/docker/testutil/request"
18 17
 	"github.com/moby/moby/api/types/container"
19 18
 	"github.com/moby/moby/api/types/system"
19
+	"github.com/moby/moby/client"
20 20
 	"gotest.tools/v3/assert"
21 21
 	"gotest.tools/v3/skip"
22 22
 )
... ...
@@ -13,13 +13,13 @@ import (
13 13
 	"testing"
14 14
 	"time"
15 15
 
16
-	"github.com/docker/docker/client"
17 16
 	eventstestutils "github.com/docker/docker/daemon/events/testutils"
18 17
 	"github.com/docker/docker/integration-cli/cli"
19 18
 	"github.com/docker/docker/integration-cli/cli/build"
20 19
 	"github.com/docker/docker/testutil"
21 20
 	"github.com/moby/moby/api/types/container"
22 21
 	eventtypes "github.com/moby/moby/api/types/events"
22
+	"github.com/moby/moby/client"
23 23
 	"gotest.tools/v3/assert"
24 24
 	is "gotest.tools/v3/assert/cmp"
25 25
 	"gotest.tools/v3/icmd"
... ...
@@ -15,10 +15,10 @@ import (
15 15
 	"testing"
16 16
 	"time"
17 17
 
18
-	"github.com/docker/docker/client"
19 18
 	"github.com/docker/docker/integration-cli/cli"
20 19
 	"github.com/docker/docker/integration-cli/cli/build"
21 20
 	"github.com/docker/docker/testutil"
21
+	"github.com/moby/moby/client"
22 22
 	"gotest.tools/v3/assert"
23 23
 	is "gotest.tools/v3/assert/cmp"
24 24
 	"gotest.tools/v3/icmd"
... ...
@@ -5,9 +5,9 @@ package main
5 5
 import (
6 6
 	"testing"
7 7
 
8
-	"github.com/docker/docker/client"
9 8
 	"github.com/docker/docker/daemon/config"
10 9
 	"github.com/docker/docker/testutil"
10
+	"github.com/moby/moby/client"
11 11
 	"gotest.tools/v3/assert"
12 12
 	is "gotest.tools/v3/assert/cmp"
13 13
 )
... ...
@@ -5,9 +5,9 @@ import (
5 5
 	"strings"
6 6
 	"testing"
7 7
 
8
-	"github.com/docker/docker/client"
9 8
 	"github.com/docker/docker/integration-cli/cli"
10 9
 	"github.com/docker/docker/testutil"
10
+	"github.com/moby/moby/client"
11 11
 	"gotest.tools/v3/assert"
12 12
 	is "gotest.tools/v3/assert/cmp"
13 13
 )
... ...
@@ -23,7 +23,6 @@ import (
23 23
 	"testing"
24 24
 	"time"
25 25
 
26
-	"github.com/docker/docker/client"
27 26
 	"github.com/docker/docker/daemon/libnetwork/resolvconf"
28 27
 	"github.com/docker/docker/integration-cli/cli"
29 28
 	"github.com/docker/docker/integration-cli/cli/build"
... ...
@@ -34,6 +33,7 @@ import (
34 34
 	testdaemon "github.com/docker/docker/testutil/daemon"
35 35
 	"github.com/docker/docker/testutil/fakecontext"
36 36
 	"github.com/docker/go-connections/nat"
37
+	"github.com/moby/moby/client"
37 38
 	"github.com/moby/sys/mountinfo"
38 39
 	"gotest.tools/v3/assert"
39 40
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -17,11 +17,11 @@ import (
17 17
 	"time"
18 18
 
19 19
 	"github.com/creack/pty"
20
-	"github.com/docker/docker/client"
21 20
 	"github.com/docker/docker/integration-cli/cli"
22 21
 	"github.com/docker/docker/integration-cli/cli/build"
23 22
 	"github.com/docker/docker/pkg/sysinfo"
24 23
 	"github.com/docker/docker/testutil"
24
+	"github.com/moby/moby/client"
25 25
 	"github.com/moby/sys/mount"
26 26
 	"gotest.tools/v3/assert"
27 27
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -12,11 +12,11 @@ import (
12 12
 	"time"
13 13
 
14 14
 	"github.com/creack/pty"
15
-	"github.com/docker/docker/client"
16 15
 	"github.com/docker/docker/integration-cli/cli"
17 16
 	"github.com/docker/docker/testutil"
18 17
 	"github.com/docker/docker/testutil/request"
19 18
 	"github.com/moby/moby/api/types/container"
19
+	"github.com/moby/moby/client"
20 20
 	"gotest.tools/v3/assert"
21 21
 	is "gotest.tools/v3/assert/cmp"
22 22
 	"gotest.tools/v3/skip"
... ...
@@ -9,13 +9,13 @@ import (
9 9
 	"strings"
10 10
 	"testing"
11 11
 
12
-	"github.com/docker/docker/client"
13 12
 	"github.com/docker/docker/integration-cli/cli"
14 13
 	"github.com/docker/docker/integration-cli/cli/build"
15 14
 	"github.com/docker/docker/testutil"
16 15
 	"github.com/moby/moby/api/types/container"
17 16
 	"github.com/moby/moby/api/types/mount"
18 17
 	"github.com/moby/moby/api/types/network"
18
+	"github.com/moby/moby/client"
19 19
 	"gotest.tools/v3/assert"
20 20
 	is "gotest.tools/v3/assert/cmp"
21 21
 	"gotest.tools/v3/icmd"
... ...
@@ -14,7 +14,6 @@ import (
14 14
 	"testing"
15 15
 	"time"
16 16
 
17
-	"github.com/docker/docker/client"
18 17
 	"github.com/docker/docker/integration-cli/cli"
19 18
 	"github.com/docker/docker/integration-cli/daemon"
20 19
 	"github.com/docker/docker/internal/testutils/specialimage"
... ...
@@ -22,6 +21,7 @@ import (
22 22
 	"github.com/moby/go-archive"
23 23
 	"github.com/moby/moby/api/types"
24 24
 	"github.com/moby/moby/api/types/container"
25
+	"github.com/moby/moby/client"
25 26
 	"gotest.tools/v3/assert"
26 27
 	is "gotest.tools/v3/assert/cmp"
27 28
 	"gotest.tools/v3/icmd"
... ...
@@ -11,12 +11,12 @@ import (
11 11
 	"time"
12 12
 
13 13
 	"github.com/containerd/containerd/v2/plugins"
14
-	"github.com/docker/docker/client"
15 14
 	"github.com/docker/docker/integration-cli/cli"
16 15
 	"github.com/docker/docker/integration-cli/requirement"
17 16
 	"github.com/docker/docker/testutil/registry"
18 17
 	"github.com/moby/moby/api/types/network"
19 18
 	"github.com/moby/moby/api/types/swarm"
19
+	"github.com/moby/moby/client"
20 20
 )
21 21
 
22 22
 func DaemonIsWindows() bool {
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"strings"
7 7
 	"testing"
8 8
 
9
-	dclient "github.com/docker/docker/client"
10 9
 	"github.com/docker/docker/integration/internal/container"
11 10
 	"github.com/docker/docker/pkg/stdcopy"
12 11
 	"github.com/docker/docker/testutil"
... ...
@@ -14,6 +13,7 @@ import (
14 14
 	"github.com/docker/docker/testutil/fakecontext"
15 15
 	"github.com/moby/moby/api/types/build"
16 16
 	containertypes "github.com/moby/moby/api/types/container"
17
+	dclient "github.com/moby/moby/client"
17 18
 	"gotest.tools/v3/assert"
18 19
 	is "gotest.tools/v3/assert/cmp"
19 20
 	"gotest.tools/v3/poll"
... ...
@@ -9,13 +9,13 @@ import (
9 9
 	"time"
10 10
 
11 11
 	cerrdefs "github.com/containerd/errdefs"
12
-	"github.com/docker/docker/client"
13 12
 	"github.com/docker/docker/integration/internal/swarm"
14 13
 	"github.com/docker/docker/pkg/stdcopy"
15 14
 	"github.com/docker/docker/testutil"
16 15
 	"github.com/moby/moby/api/types/container"
17 16
 	"github.com/moby/moby/api/types/filters"
18 17
 	swarmtypes "github.com/moby/moby/api/types/swarm"
18
+	"github.com/moby/moby/client"
19 19
 	"gotest.tools/v3/assert"
20 20
 	is "gotest.tools/v3/assert/cmp"
21 21
 	"gotest.tools/v3/poll"
... ...
@@ -7,12 +7,12 @@ import (
7 7
 	"sort"
8 8
 	"testing"
9 9
 
10
-	"github.com/docker/docker/client"
11 10
 	"github.com/docker/docker/integration/internal/container"
12 11
 	"github.com/docker/docker/testutil/request"
13 12
 	"github.com/moby/moby/api/types/checkpoint"
14 13
 	containertypes "github.com/moby/moby/api/types/container"
15 14
 	mounttypes "github.com/moby/moby/api/types/mount"
15
+	"github.com/moby/moby/client"
16 16
 	"gotest.tools/v3/assert"
17 17
 	is "gotest.tools/v3/assert/cmp"
18 18
 	"gotest.tools/v3/poll"
... ...
@@ -11,7 +11,6 @@ import (
11 11
 
12 12
 	containerd "github.com/containerd/containerd/v2/client"
13 13
 	cerrdefs "github.com/containerd/errdefs"
14
-	"github.com/docker/docker/client"
15 14
 	testContainer "github.com/docker/docker/integration/internal/container"
16 15
 	net "github.com/docker/docker/integration/internal/network"
17 16
 	"github.com/docker/docker/oci"
... ...
@@ -20,6 +19,7 @@ import (
20 20
 	"github.com/moby/moby/api/types/container"
21 21
 	"github.com/moby/moby/api/types/network"
22 22
 	"github.com/moby/moby/api/types/versions"
23
+	"github.com/moby/moby/client"
23 24
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
24 25
 	"gotest.tools/v3/assert"
25 26
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -6,9 +6,9 @@ import (
6 6
 	"testing"
7 7
 	"time"
8 8
 
9
-	"github.com/docker/docker/client"
10 9
 	"github.com/docker/docker/integration/internal/container"
11 10
 	containertypes "github.com/moby/moby/api/types/container"
11
+	"github.com/moby/moby/client"
12 12
 	"gotest.tools/v3/assert"
13 13
 	"gotest.tools/v3/poll"
14 14
 	"gotest.tools/v3/skip"
... ...
@@ -7,10 +7,10 @@ import (
7 7
 	"testing"
8 8
 
9 9
 	"github.com/containerd/platforms"
10
-	"github.com/docker/docker/client"
11 10
 	"github.com/docker/docker/integration/internal/container"
12 11
 	"github.com/docker/docker/testutil/request"
13 12
 	containertypes "github.com/moby/moby/api/types/container"
13
+	"github.com/moby/moby/client"
14 14
 	"gotest.tools/v3/assert"
15 15
 	is "gotest.tools/v3/assert/cmp"
16 16
 	"gotest.tools/v3/skip"
... ...
@@ -7,13 +7,13 @@ import (
7 7
 	"strings"
8 8
 	"testing"
9 9
 
10
-	"github.com/docker/docker/client"
11 10
 	"github.com/docker/docker/integration/internal/container"
12 11
 	"github.com/docker/docker/testutil"
13 12
 	"github.com/docker/docker/testutil/daemon"
14 13
 	"github.com/docker/docker/testutil/request"
15 14
 	containertypes "github.com/moby/moby/api/types/container"
16 15
 	"github.com/moby/moby/api/types/versions"
16
+	"github.com/moby/moby/client"
17 17
 	"gotest.tools/v3/assert"
18 18
 	is "gotest.tools/v3/assert/cmp"
19 19
 	"gotest.tools/v3/fs"
... ...
@@ -4,12 +4,12 @@ import (
4 4
 	"fmt"
5 5
 	"testing"
6 6
 
7
-	"github.com/docker/docker/client"
8 7
 	"github.com/docker/docker/integration/internal/container"
9 8
 	"github.com/docker/docker/testutil"
10 9
 	"github.com/docker/docker/testutil/request"
11 10
 	containertypes "github.com/moby/moby/api/types/container"
12 11
 	"github.com/moby/moby/api/types/filters"
12
+	"github.com/moby/moby/client"
13 13
 	"gotest.tools/v3/assert"
14 14
 	is "gotest.tools/v3/assert/cmp"
15 15
 	"gotest.tools/v3/skip"
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"testing"
9 9
 
10 10
 	cerrdefs "github.com/containerd/errdefs"
11
-	"github.com/docker/docker/client"
12 11
 	"github.com/docker/docker/daemon/volume"
13 12
 	"github.com/docker/docker/integration/internal/container"
14 13
 	"github.com/docker/docker/pkg/parsers/kernel"
... ...
@@ -18,6 +17,7 @@ import (
18 18
 	mounttypes "github.com/moby/moby/api/types/mount"
19 19
 	"github.com/moby/moby/api/types/network"
20 20
 	"github.com/moby/moby/api/types/versions"
21
+	"github.com/moby/moby/client"
21 22
 	"github.com/moby/sys/mount"
22 23
 	"github.com/moby/sys/mountinfo"
23 24
 	"gotest.tools/v3/assert"
... ...
@@ -7,13 +7,13 @@ import (
7 7
 	"testing"
8 8
 	"time"
9 9
 
10
-	"github.com/docker/docker/client"
11 10
 	testContainer "github.com/docker/docker/integration/internal/container"
12 11
 	"github.com/docker/docker/testutil"
13 12
 	"github.com/docker/docker/testutil/daemon"
14 13
 	"github.com/moby/moby/api/types/container"
15 14
 	"github.com/moby/moby/api/types/events"
16 15
 	"github.com/moby/moby/api/types/filters"
16
+	"github.com/moby/moby/client"
17 17
 	"gotest.tools/v3/assert"
18 18
 	is "gotest.tools/v3/assert/cmp"
19 19
 	"gotest.tools/v3/poll"
... ...
@@ -4,11 +4,11 @@ import (
4 4
 	"context"
5 5
 	"testing"
6 6
 
7
-	"github.com/docker/docker/client"
8 7
 	"github.com/docker/docker/integration/internal/container"
9 8
 	"github.com/docker/docker/integration/internal/requirement"
10 9
 	"github.com/docker/docker/testutil"
11 10
 	"github.com/docker/docker/testutil/daemon"
11
+	"github.com/moby/moby/client"
12 12
 	"gotest.tools/v3/assert"
13 13
 	"gotest.tools/v3/skip"
14 14
 )
... ...
@@ -9,7 +9,6 @@ import (
9 9
 	"strings"
10 10
 	"testing"
11 11
 
12
-	"github.com/docker/docker/client"
13 12
 	"github.com/docker/docker/integration/internal/container"
14 13
 	net "github.com/docker/docker/integration/internal/network"
15 14
 	"github.com/docker/docker/pkg/stdcopy"
... ...
@@ -17,6 +16,7 @@ import (
17 17
 	"github.com/docker/docker/testutil/daemon"
18 18
 	containertypes "github.com/moby/moby/api/types/container"
19 19
 	"github.com/moby/moby/api/types/versions"
20
+	"github.com/moby/moby/client"
20 21
 	"golang.org/x/sys/unix"
21 22
 	"gotest.tools/v3/assert"
22 23
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -9,10 +9,10 @@ import (
9 9
 	"time"
10 10
 
11 11
 	cerrdefs "github.com/containerd/errdefs"
12
-	"github.com/docker/docker/client"
13 12
 	"github.com/docker/docker/integration/internal/container"
14 13
 	"github.com/docker/docker/pkg/stdcopy"
15 14
 	containertypes "github.com/moby/moby/api/types/container"
15
+	"github.com/moby/moby/client"
16 16
 	"gotest.tools/v3/assert"
17 17
 	is "gotest.tools/v3/assert/cmp"
18 18
 	"gotest.tools/v3/poll"
... ...
@@ -7,11 +7,11 @@ import (
7 7
 	"testing"
8 8
 	"time"
9 9
 
10
-	"github.com/docker/docker/client"
11 10
 	"github.com/docker/docker/integration/internal/container"
12 11
 	"github.com/docker/docker/testutil"
13 12
 	"github.com/docker/docker/testutil/request"
14 13
 	containertypes "github.com/moby/moby/api/types/container"
14
+	"github.com/moby/moby/client"
15 15
 	"gotest.tools/v3/assert"
16 16
 	is "gotest.tools/v3/assert/cmp"
17 17
 	"gotest.tools/v3/skip"
... ...
@@ -5,10 +5,10 @@ import (
5 5
 	"encoding/json"
6 6
 	"testing"
7 7
 
8
-	"github.com/docker/docker/client"
9 8
 	iimage "github.com/docker/docker/integration/internal/image"
10 9
 	"github.com/docker/docker/internal/testutils/specialimage"
11 10
 	"github.com/moby/moby/api/types/image"
11
+	"github.com/moby/moby/client"
12 12
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
13 13
 	"gotest.tools/v3/assert"
14 14
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"testing"
8 8
 	"time"
9 9
 
10
-	"github.com/docker/docker/client"
11 10
 	"github.com/docker/docker/integration/internal/container"
12 11
 	iimage "github.com/docker/docker/integration/internal/image"
13 12
 	"github.com/docker/docker/internal/testutils/specialimage"
... ...
@@ -19,6 +18,7 @@ import (
19 19
 	"github.com/moby/moby/api/types/filters"
20 20
 	"github.com/moby/moby/api/types/image"
21 21
 	"github.com/moby/moby/api/types/versions"
22
+	"github.com/moby/moby/client"
22 23
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
23 24
 	"gotest.tools/v3/assert"
24 25
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"strings"
5 5
 	"testing"
6 6
 
7
-	"github.com/docker/docker/client"
8 7
 	"github.com/docker/docker/integration/internal/container"
9 8
 	iimage "github.com/docker/docker/integration/internal/image"
10 9
 	"github.com/docker/docker/internal/testutils/specialimage"
... ...
@@ -13,6 +12,7 @@ import (
13 13
 	containertypes "github.com/moby/moby/api/types/container"
14 14
 	"github.com/moby/moby/api/types/filters"
15 15
 	"github.com/moby/moby/api/types/image"
16
+	"github.com/moby/moby/client"
16 17
 	"gotest.tools/v3/assert"
17 18
 	is "gotest.tools/v3/assert/cmp"
18 19
 	"gotest.tools/v3/skip"
... ...
@@ -15,7 +15,6 @@ import (
15 15
 
16 16
 	cerrdefs "github.com/containerd/errdefs"
17 17
 	"github.com/cpuguy83/tar2go"
18
-	"github.com/docker/docker/client"
19 18
 	"github.com/docker/docker/integration/internal/build"
20 19
 	"github.com/docker/docker/integration/internal/container"
21 20
 	iimage "github.com/docker/docker/integration/internal/image"
... ...
@@ -25,6 +24,7 @@ import (
25 25
 	"github.com/moby/go-archive/compression"
26 26
 	containertypes "github.com/moby/moby/api/types/container"
27 27
 	"github.com/moby/moby/api/types/versions"
28
+	"github.com/moby/moby/client"
28 29
 	"github.com/opencontainers/go-digest"
29 30
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
30 31
 	"gotest.tools/v3/assert"
... ...
@@ -6,11 +6,11 @@ import (
6 6
 	"io"
7 7
 	"testing"
8 8
 
9
-	"github.com/docker/docker/client"
10 9
 	"github.com/docker/docker/pkg/jsonmessage"
11 10
 	"github.com/docker/docker/testutil/fakecontext"
12 11
 	"github.com/moby/moby/api/types/build"
13 12
 	"github.com/moby/moby/api/types/image"
13
+	"github.com/moby/moby/client"
14 14
 	"gotest.tools/v3/assert"
15 15
 )
16 16
 
... ...
@@ -8,11 +8,11 @@ import (
8 8
 	"sync"
9 9
 	"testing"
10 10
 
11
-	"github.com/docker/docker/client"
12 11
 	"github.com/docker/docker/pkg/stdcopy"
13 12
 	"github.com/moby/moby/api/types"
14 13
 	"github.com/moby/moby/api/types/container"
15 14
 	"github.com/moby/moby/api/types/network"
15
+	"github.com/moby/moby/client"
16 16
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
17 17
 	"gotest.tools/v3/assert"
18 18
 )
... ...
@@ -5,8 +5,8 @@ import (
5 5
 	"context"
6 6
 	"testing"
7 7
 
8
-	"github.com/docker/docker/client"
9 8
 	"github.com/moby/moby/api/types/container"
9
+	"github.com/moby/moby/client"
10 10
 )
11 11
 
12 12
 // ExecResult represents a result returned from Exec()
... ...
@@ -5,7 +5,7 @@ import (
5 5
 	"strings"
6 6
 	"testing"
7 7
 
8
-	"github.com/docker/docker/client"
8
+	"github.com/moby/moby/client"
9 9
 	"gotest.tools/v3/assert"
10 10
 	is "gotest.tools/v3/assert/cmp"
11 11
 )
... ...
@@ -5,8 +5,8 @@ import (
5 5
 	"strings"
6 6
 
7 7
 	cerrdefs "github.com/containerd/errdefs"
8
-	"github.com/docker/docker/client"
9 8
 	"github.com/moby/moby/api/types/container"
9
+	"github.com/moby/moby/client"
10 10
 	"github.com/pkg/errors"
11 11
 	"gotest.tools/v3/poll"
12 12
 )
... ...
@@ -9,10 +9,10 @@ import (
9 9
 	"strings"
10 10
 	"testing"
11 11
 
12
-	"github.com/docker/docker/client"
13 12
 	"github.com/docker/docker/internal/testutils/specialimage"
14 13
 	"github.com/docker/docker/pkg/jsonmessage"
15 14
 	"github.com/moby/go-archive"
15
+	"github.com/moby/moby/client"
16 16
 	"gotest.tools/v3/assert"
17 17
 )
18 18
 
... ...
@@ -4,8 +4,8 @@ import (
4 4
 	"context"
5 5
 	"testing"
6 6
 
7
-	"github.com/docker/docker/client"
8 7
 	"github.com/moby/moby/api/types/network"
8
+	"github.com/moby/moby/client"
9 9
 	"gotest.tools/v3/assert"
10 10
 )
11 11
 
... ...
@@ -3,8 +3,8 @@ package network
3 3
 import (
4 4
 	"context"
5 5
 
6
-	"github.com/docker/docker/client"
7 6
 	"github.com/moby/moby/api/types/network"
7
+	"github.com/moby/moby/client"
8 8
 	"gotest.tools/v3/poll"
9 9
 )
10 10
 
... ...
@@ -6,13 +6,13 @@ import (
6 6
 	"testing"
7 7
 	"time"
8 8
 
9
-	"github.com/docker/docker/client"
10 9
 	"github.com/docker/docker/testutil/daemon"
11 10
 	"github.com/docker/docker/testutil/environment"
12 11
 	"github.com/moby/moby/api/types"
13 12
 	"github.com/moby/moby/api/types/container"
14 13
 	"github.com/moby/moby/api/types/filters"
15 14
 	swarmtypes "github.com/moby/moby/api/types/swarm"
15
+	"github.com/moby/moby/client"
16 16
 	"gotest.tools/v3/assert"
17 17
 	"gotest.tools/v3/poll"
18 18
 	"gotest.tools/v3/skip"
... ...
@@ -4,9 +4,9 @@ import (
4 4
 	"context"
5 5
 	"fmt"
6 6
 
7
-	"github.com/docker/docker/client"
8 7
 	"github.com/moby/moby/api/types/filters"
9 8
 	swarmtypes "github.com/moby/moby/api/types/swarm"
9
+	"github.com/moby/moby/client"
10 10
 	"gotest.tools/v3/poll"
11 11
 )
12 12
 
... ...
@@ -4,7 +4,7 @@ import (
4 4
 	"context"
5 5
 	"time"
6 6
 
7
-	"github.com/docker/docker/client"
7
+	"github.com/moby/moby/client"
8 8
 	"gotest.tools/v3/poll"
9 9
 )
10 10
 
... ...
@@ -30,7 +30,6 @@ import (
30 30
 	"text/template"
31 31
 	"time"
32 32
 
33
-	"github.com/docker/docker/client"
34 33
 	"github.com/docker/docker/daemon/libnetwork/drivers/bridge"
35 34
 	"github.com/docker/docker/integration/internal/container"
36 35
 	"github.com/docker/docker/integration/internal/network"
... ...
@@ -41,6 +40,7 @@ import (
41 41
 	containertypes "github.com/moby/moby/api/types/container"
42 42
 	networktypes "github.com/moby/moby/api/types/network"
43 43
 	swarmtypes "github.com/moby/moby/api/types/swarm"
44
+	"github.com/moby/moby/client"
44 45
 	"github.com/vishvananda/netlink"
45 46
 	"gotest.tools/v3/assert"
46 47
 	"gotest.tools/v3/golden"
... ...
@@ -4,9 +4,9 @@ import (
4 4
 	"context"
5 5
 	"testing"
6 6
 
7
-	dclient "github.com/docker/docker/client"
8 7
 	"github.com/docker/docker/integration/internal/network"
9 8
 	networktypes "github.com/moby/moby/api/types/network"
9
+	dclient "github.com/moby/moby/client"
10 10
 	"gotest.tools/v3/assert"
11 11
 	is "gotest.tools/v3/assert/cmp"
12 12
 	"gotest.tools/v3/skip"
... ...
@@ -7,9 +7,9 @@ import (
7 7
 	"fmt"
8 8
 	"testing"
9 9
 
10
-	"github.com/docker/docker/client"
11 10
 	"github.com/docker/docker/testutil"
12 11
 	"github.com/moby/moby/api/types/network"
12
+	"github.com/moby/moby/client"
13 13
 	is "gotest.tools/v3/assert/cmp"
14 14
 	"gotest.tools/v3/icmd"
15 15
 )
... ...
@@ -4,8 +4,8 @@ import (
4 4
 	"context"
5 5
 	"fmt"
6 6
 
7
-	"github.com/docker/docker/client"
8 7
 	"github.com/moby/moby/api/types/network"
8
+	"github.com/moby/moby/client"
9 9
 	"gotest.tools/v3/assert/cmp"
10 10
 )
11 11
 
... ...
@@ -9,7 +9,6 @@ import (
9 9
 	"testing"
10 10
 	"time"
11 11
 
12
-	dclient "github.com/docker/docker/client"
13 12
 	"github.com/docker/docker/daemon/libnetwork/netlabel"
14 13
 	"github.com/docker/docker/integration/internal/container"
15 14
 	net "github.com/docker/docker/integration/internal/network"
... ...
@@ -18,6 +17,7 @@ import (
18 18
 	"github.com/docker/docker/testutil/daemon"
19 19
 	containertypes "github.com/moby/moby/api/types/container"
20 20
 	"github.com/moby/moby/api/types/network"
21
+	dclient "github.com/moby/moby/client"
21 22
 	"gotest.tools/v3/assert"
22 23
 	is "gotest.tools/v3/assert/cmp"
23 24
 	"gotest.tools/v3/skip"
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"testing"
9 9
 	"time"
10 10
 
11
-	"github.com/docker/docker/client"
12 11
 	"github.com/docker/docker/daemon/libnetwork/netlabel"
13 12
 	"github.com/docker/docker/integration/internal/container"
14 13
 	net "github.com/docker/docker/integration/internal/network"
... ...
@@ -17,6 +16,7 @@ import (
17 17
 	"github.com/docker/docker/testutil/daemon"
18 18
 	containertypes "github.com/moby/moby/api/types/container"
19 19
 	"github.com/moby/moby/api/types/network"
20
+	"github.com/moby/moby/client"
20 21
 	"gotest.tools/v3/assert"
21 22
 	is "gotest.tools/v3/assert/cmp"
22 23
 	"gotest.tools/v3/icmd"
... ...
@@ -11,7 +11,6 @@ import (
11 11
 	"testing"
12 12
 	"time"
13 13
 
14
-	"github.com/docker/docker/client"
15 14
 	"github.com/docker/docker/daemon/libnetwork/netlabel"
16 15
 	"github.com/docker/docker/integration/internal/container"
17 16
 	"github.com/docker/docker/integration/internal/network"
... ...
@@ -21,6 +20,7 @@ import (
21 21
 	containertypes "github.com/moby/moby/api/types/container"
22 22
 	networktypes "github.com/moby/moby/api/types/network"
23 23
 	"github.com/moby/moby/api/types/versions"
24
+	"github.com/moby/moby/client"
24 25
 	"gotest.tools/v3/assert"
25 26
 	is "gotest.tools/v3/assert/cmp"
26 27
 	"gotest.tools/v3/icmd"
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"testing"
7 7
 	"time"
8 8
 
9
-	"github.com/docker/docker/client"
10 9
 	"github.com/docker/docker/daemon/libnetwork/netlabel"
11 10
 	"github.com/docker/docker/integration/internal/container"
12 11
 	"github.com/docker/docker/integration/internal/network"
... ...
@@ -16,6 +15,7 @@ import (
16 16
 	containertypes "github.com/moby/moby/api/types/container"
17 17
 	networktypes "github.com/moby/moby/api/types/network"
18 18
 	swarmtypes "github.com/moby/moby/api/types/swarm"
19
+	"github.com/moby/moby/client"
19 20
 	"gotest.tools/v3/assert"
20 21
 	is "gotest.tools/v3/assert/cmp"
21 22
 	"gotest.tools/v3/icmd"
... ...
@@ -16,7 +16,6 @@ import (
16 16
 	"testing"
17 17
 	"time"
18 18
 
19
-	"github.com/docker/docker/client"
20 19
 	"github.com/docker/docker/daemon/libnetwork/drivers/bridge"
21 20
 	"github.com/docker/docker/daemon/libnetwork/iptables"
22 21
 	"github.com/docker/docker/daemon/libnetwork/netlabel"
... ...
@@ -30,6 +29,7 @@ import (
30 30
 	"github.com/google/go-cmp/cmp/cmpopts"
31 31
 	containertypes "github.com/moby/moby/api/types/container"
32 32
 	networktypes "github.com/moby/moby/api/types/network"
33
+	"github.com/moby/moby/client"
33 34
 	"gotest.tools/v3/assert"
34 35
 	is "gotest.tools/v3/assert/cmp"
35 36
 	"gotest.tools/v3/skip"
... ...
@@ -4,9 +4,9 @@ import (
4 4
 	"os"
5 5
 	"testing"
6 6
 
7
-	"github.com/docker/docker/client"
8 7
 	"github.com/docker/docker/internal/testutils/networking"
9 8
 	"github.com/docker/docker/testutil/request"
9
+	"github.com/moby/moby/client"
10 10
 	"gotest.tools/v3/assert"
11 11
 	is "gotest.tools/v3/assert/cmp"
12 12
 )
... ...
@@ -3,13 +3,13 @@ package networking
3 3
 import (
4 4
 	"testing"
5 5
 
6
-	"github.com/docker/docker/client"
7 6
 	"github.com/docker/docker/daemon/libnetwork/drivers/bridge"
8 7
 	"github.com/docker/docker/integration/internal/container"
9 8
 	"github.com/docker/docker/integration/internal/network"
10 9
 	"github.com/docker/docker/testutil"
11 10
 	"github.com/docker/docker/testutil/daemon"
12 11
 	containertypes "github.com/moby/moby/api/types/container"
12
+	"github.com/moby/moby/client"
13 13
 	"gotest.tools/v3/assert"
14 14
 	is "gotest.tools/v3/assert/cmp"
15 15
 	"gotest.tools/v3/skip"
... ...
@@ -15,7 +15,6 @@ import (
15 15
 	"testing"
16 16
 	"time"
17 17
 
18
-	"github.com/docker/docker/client"
19 18
 	"github.com/docker/docker/daemon/libnetwork/drivers/bridge"
20 19
 	"github.com/docker/docker/integration/internal/container"
21 20
 	"github.com/docker/docker/integration/internal/network"
... ...
@@ -26,6 +25,7 @@ import (
26 26
 	"github.com/docker/go-connections/nat"
27 27
 	containertypes "github.com/moby/moby/api/types/container"
28 28
 	networktypes "github.com/moby/moby/api/types/network"
29
+	"github.com/moby/moby/client"
29 30
 	"gotest.tools/v3/assert"
30 31
 	is "gotest.tools/v3/assert/cmp"
31 32
 	"gotest.tools/v3/golden"
... ...
@@ -17,7 +17,6 @@ import (
17 17
 	"testing"
18 18
 	"time"
19 19
 
20
-	"github.com/docker/docker/client"
21 20
 	"github.com/docker/docker/integration/internal/container"
22 21
 	"github.com/docker/docker/pkg/authorization"
23 22
 	"github.com/docker/docker/testutil/environment"
... ...
@@ -26,6 +25,7 @@ import (
26 26
 	containertypes "github.com/moby/moby/api/types/container"
27 27
 	eventtypes "github.com/moby/moby/api/types/events"
28 28
 	"github.com/moby/moby/api/types/image"
29
+	"github.com/moby/moby/client"
29 30
 	"gotest.tools/v3/assert"
30 31
 	"gotest.tools/v3/skip"
31 32
 )
... ...
@@ -8,12 +8,12 @@ import (
8 8
 	"io"
9 9
 	"testing"
10 10
 
11
-	"github.com/docker/docker/client"
12 11
 	"github.com/docker/docker/integration/internal/container"
13 12
 	"github.com/docker/docker/integration/internal/requirement"
14 13
 	"github.com/moby/moby/api/types"
15 14
 	"github.com/moby/moby/api/types/filters"
16 15
 	"github.com/moby/moby/api/types/volume"
16
+	"github.com/moby/moby/client"
17 17
 	"gotest.tools/v3/assert"
18 18
 	"gotest.tools/v3/skip"
19 19
 )
... ...
@@ -9,13 +9,13 @@ import (
9 9
 	"time"
10 10
 
11 11
 	cerrdefs "github.com/containerd/errdefs"
12
-	"github.com/docker/docker/client"
13 12
 	"github.com/docker/docker/integration/internal/swarm"
14 13
 	"github.com/docker/docker/pkg/stdcopy"
15 14
 	"github.com/docker/docker/testutil"
16 15
 	"github.com/moby/moby/api/types/container"
17 16
 	"github.com/moby/moby/api/types/filters"
18 17
 	swarmtypes "github.com/moby/moby/api/types/swarm"
18
+	"github.com/moby/moby/client"
19 19
 	"gotest.tools/v3/assert"
20 20
 	is "gotest.tools/v3/assert/cmp"
21 21
 	"gotest.tools/v3/poll"
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"time"
9 9
 
10 10
 	cerrdefs "github.com/containerd/errdefs"
11
-	"github.com/docker/docker/client"
12 11
 	"github.com/docker/docker/integration/internal/network"
13 12
 	"github.com/docker/docker/integration/internal/swarm"
14 13
 	"github.com/docker/docker/testutil"
... ...
@@ -16,6 +15,7 @@ import (
16 16
 	"github.com/moby/moby/api/types/container"
17 17
 	"github.com/moby/moby/api/types/filters"
18 18
 	swarmtypes "github.com/moby/moby/api/types/swarm"
19
+	"github.com/moby/moby/client"
19 20
 	"gotest.tools/v3/assert"
20 21
 	is "gotest.tools/v3/assert/cmp"
21 22
 	"gotest.tools/v3/poll"
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"testing"
8 8
 	"time"
9 9
 
10
-	"github.com/docker/docker/client"
11 10
 	"github.com/docker/docker/daemon/libnetwork/scope"
12 11
 	"github.com/docker/docker/integration/internal/container"
13 12
 	net "github.com/docker/docker/integration/internal/network"
... ...
@@ -17,6 +16,7 @@ import (
17 17
 	containertypes "github.com/moby/moby/api/types/container"
18 18
 	"github.com/moby/moby/api/types/network"
19 19
 	swarmtypes "github.com/moby/moby/api/types/swarm"
20
+	"github.com/moby/moby/client"
20 21
 	"gotest.tools/v3/assert"
21 22
 	is "gotest.tools/v3/assert/cmp"
22 23
 	"gotest.tools/v3/golden"
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"context"
5 5
 	"testing"
6 6
 
7
-	"github.com/docker/docker/client"
8 7
 	"github.com/docker/docker/integration/internal/network"
9 8
 	"github.com/docker/docker/integration/internal/swarm"
10 9
 	"github.com/docker/docker/testutil"
... ...
@@ -12,6 +11,7 @@ import (
12 12
 	"github.com/moby/moby/api/types/filters"
13 13
 	networktypes "github.com/moby/moby/api/types/network"
14 14
 	swarmtypes "github.com/moby/moby/api/types/swarm"
15
+	"github.com/moby/moby/client"
15 16
 	"gotest.tools/v3/assert"
16 17
 	is "gotest.tools/v3/assert/cmp"
17 18
 	"gotest.tools/v3/poll"
... ...
@@ -8,8 +8,8 @@ import (
8 8
 	"net/http"
9 9
 	"testing"
10 10
 
11
-	"github.com/docker/docker/client"
12 11
 	"github.com/docker/docker/testutil/request"
12
+	"github.com/moby/moby/client"
13 13
 	"gotest.tools/v3/assert"
14 14
 	is "gotest.tools/v3/assert/cmp"
15 15
 )
... ...
@@ -9,7 +9,6 @@ import (
9 9
 	"strings"
10 10
 	"testing"
11 11
 
12
-	"github.com/docker/docker/client"
13 12
 	"github.com/docker/docker/daemon/volume/safepath"
14 13
 	"github.com/docker/docker/integration/internal/container"
15 14
 	"github.com/docker/docker/testutil/fakecontext"
... ...
@@ -20,6 +19,7 @@ import (
20 20
 	"github.com/moby/moby/api/types/network"
21 21
 	"github.com/moby/moby/api/types/versions"
22 22
 	"github.com/moby/moby/api/types/volume"
23
+	"github.com/moby/moby/client"
23 24
 	"gotest.tools/v3/assert"
24 25
 	is "gotest.tools/v3/assert/cmp"
25 26
 	"gotest.tools/v3/skip"
... ...
@@ -9,7 +9,6 @@ import (
9 9
 	"time"
10 10
 
11 11
 	cerrdefs "github.com/containerd/errdefs"
12
-	clientpkg "github.com/docker/docker/client"
13 12
 	"github.com/docker/docker/integration/internal/build"
14 13
 	"github.com/docker/docker/integration/internal/container"
15 14
 	"github.com/docker/docker/testutil"
... ...
@@ -20,6 +19,7 @@ import (
20 20
 	containertypes "github.com/moby/moby/api/types/container"
21 21
 	"github.com/moby/moby/api/types/filters"
22 22
 	"github.com/moby/moby/api/types/volume"
23
+	clientpkg "github.com/moby/moby/client"
23 24
 	"gotest.tools/v3/assert"
24 25
 	is "gotest.tools/v3/assert/cmp"
25 26
 	"gotest.tools/v3/skip"
... ...
@@ -21,7 +21,6 @@ import (
21 21
 	"testing"
22 22
 	"time"
23 23
 
24
-	"github.com/docker/docker/client"
25 24
 	"github.com/docker/docker/daemon/container"
26 25
 	"github.com/docker/docker/pkg/ioutils"
27 26
 	"github.com/docker/docker/pkg/stringid"
... ...
@@ -31,6 +30,7 @@ import (
31 31
 	"github.com/docker/go-connections/tlsconfig"
32 32
 	"github.com/moby/moby/api/types/events"
33 33
 	"github.com/moby/moby/api/types/system"
34
+	"github.com/moby/moby/client"
34 35
 	"github.com/pkg/errors"
35 36
 	"gotest.tools/v3/assert"
36 37
 	"gotest.tools/v3/poll"
... ...
@@ -5,8 +5,8 @@ import (
5 5
 	"testing"
6 6
 
7 7
 	cerrdefs "github.com/containerd/errdefs"
8
-	"github.com/docker/docker/client"
9 8
 	"github.com/moby/moby/api/types"
9
+	"github.com/moby/moby/client"
10 10
 	"gotest.tools/v3/poll"
11 11
 )
12 12
 
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"testing"
7 7
 
8 8
 	cerrdefs "github.com/containerd/errdefs"
9
-	"github.com/docker/docker/client"
10 9
 	"github.com/docker/docker/internal/lazyregexp"
11 10
 	"github.com/moby/moby/api/types"
12 11
 	"github.com/moby/moby/api/types/container"
... ...
@@ -14,6 +13,7 @@ import (
14 14
 	"github.com/moby/moby/api/types/image"
15 15
 	"github.com/moby/moby/api/types/network"
16 16
 	"github.com/moby/moby/api/types/volume"
17
+	"github.com/moby/moby/client"
17 18
 	"go.opentelemetry.io/otel"
18 19
 	"gotest.tools/v3/assert"
19 20
 )
... ...
@@ -8,12 +8,12 @@ import (
8 8
 	"strings"
9 9
 	"testing"
10 10
 
11
-	"github.com/docker/docker/client"
12 11
 	"github.com/docker/docker/testutil/fixtures/load"
13 12
 	"github.com/moby/moby/api/types"
14 13
 	"github.com/moby/moby/api/types/filters"
15 14
 	"github.com/moby/moby/api/types/image"
16 15
 	"github.com/moby/moby/api/types/system"
16
+	"github.com/moby/moby/client"
17 17
 	"github.com/pkg/errors"
18 18
 	"gotest.tools/v3/assert"
19 19
 )
... ...
@@ -11,7 +11,6 @@ import (
11 11
 	"strings"
12 12
 	"testing"
13 13
 
14
-	"github.com/docker/docker/client"
15 14
 	"github.com/docker/docker/testutil"
16 15
 	"github.com/docker/docker/testutil/environment"
17 16
 	"github.com/docker/docker/testutil/fakecontext"
... ...
@@ -20,6 +19,7 @@ import (
20 20
 	"github.com/moby/moby/api/types/build"
21 21
 	containertypes "github.com/moby/moby/api/types/container"
22 22
 	"github.com/moby/moby/api/types/image"
23
+	"github.com/moby/moby/client"
23 24
 	"gotest.tools/v3/assert"
24 25
 )
25 26
 
... ...
@@ -10,9 +10,9 @@ import (
10 10
 	"strings"
11 11
 	"sync"
12 12
 
13
-	"github.com/docker/docker/client"
14 13
 	"github.com/docker/docker/pkg/jsonmessage"
15 14
 	"github.com/moby/moby/api/types/image"
15
+	"github.com/moby/moby/client"
16 16
 	"github.com/moby/term"
17 17
 	"github.com/pkg/errors"
18 18
 	"go.opentelemetry.io/otel"
... ...
@@ -13,11 +13,11 @@ import (
13 13
 	"testing"
14 14
 	"time"
15 15
 
16
-	"github.com/docker/docker/client"
17 16
 	"github.com/docker/docker/pkg/ioutils"
18 17
 	"github.com/docker/docker/testutil/environment"
19 18
 	"github.com/docker/go-connections/sockets"
20 19
 	"github.com/docker/go-connections/tlsconfig"
20
+	"github.com/moby/moby/client"
21 21
 	"github.com/pkg/errors"
22 22
 	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
23 23
 	"gotest.tools/v3/assert"
... ...
@@ -30,7 +30,7 @@ require (
30 30
 	github.com/containerd/containerd/v2 v2.1.3
31 31
 	github.com/containerd/continuity v0.4.5
32 32
 	github.com/containerd/errdefs v1.0.0
33
-	github.com/containerd/errdefs/pkg v0.3.0
33
+	github.com/containerd/errdefs/pkg v0.3.0 // indirect
34 34
 	github.com/containerd/fifo v1.1.0
35 35
 	github.com/containerd/log v0.1.0
36 36
 	github.com/containerd/platforms v1.0.0-rc.1
... ...
@@ -191,6 +191,7 @@ require (
191 191
 	github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
192 192
 	github.com/mitchellh/reflectwalk v1.0.2 // indirect
193 193
 	github.com/moby/moby/api v0.0.0
194
+	github.com/moby/moby/client v0.0.0
194 195
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
195 196
 	github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 // indirect
196 197
 	github.com/package-url/packageurl-go v0.1.1 // indirect
... ...
@@ -264,3 +265,5 @@ exclude (
264 264
 )
265 265
 
266 266
 replace github.com/moby/moby/api => ./api
267
+
268
+replace github.com/moby/moby/client => ./client
267 269
new file mode 100644
... ...
@@ -0,0 +1,38 @@
0
+# Go client for the Docker Engine API
1
+
2
+The `docker` command uses this package to communicate with the daemon. It can
3
+also be used by your own Go applications to do anything the command-line
4
+interface does – running containers, pulling images, managing swarms, etc.
5
+
6
+For example, to list all containers (the equivalent of `docker ps --all`):
7
+
8
+```go
9
+package main
10
+
11
+import (
12
+	"context"
13
+	"fmt"
14
+
15
+	"github.com/docker/docker/api/types/container"
16
+	"github.com/docker/docker/client"
17
+)
18
+
19
+func main() {
20
+	apiClient, err := client.NewClientWithOpts(client.FromEnv)
21
+	if err != nil {
22
+		panic(err)
23
+	}
24
+	defer apiClient.Close()
25
+
26
+	containers, err := apiClient.ContainerList(context.Background(), container.ListOptions{All: true})
27
+	if err != nil {
28
+		panic(err)
29
+	}
30
+
31
+	for _, ctr := range containers {
32
+		fmt.Printf("%s %s (status: %s)\n", ctr.ID, ctr.Image, ctr.Status)
33
+	}
34
+}
35
+```
36
+
37
+[Full documentation is available on pkg.go.dev.](https://pkg.go.dev/github.com/docker/docker/client)
0 38
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/moby/moby/api/types/registry"
6
+)
7
+
8
+// staticAuth creates a privilegeFn from the given registryAuth.
9
+func staticAuth(registryAuth string) registry.RequestAuthConfig {
10
+	return func(ctx context.Context) (string, error) {
11
+		return registryAuth, nil
12
+	}
13
+}
0 14
new file mode 100644
... ...
@@ -0,0 +1,16 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+)
6
+
7
+// BuildCancel requests the daemon to cancel the ongoing build request.
8
+func (cli *Client) BuildCancel(ctx context.Context, id string) error {
9
+	query := url.Values{}
10
+	query.Set("id", id)
11
+
12
+	resp, err := cli.post(ctx, "/build/cancel", query, nil, nil)
13
+	ensureReaderClosed(resp)
14
+	return err
15
+}
0 16
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+	"strconv"
7
+
8
+	"github.com/moby/moby/api/types/build"
9
+	"github.com/moby/moby/api/types/filters"
10
+	"github.com/pkg/errors"
11
+)
12
+
13
+// BuildCachePrune requests the daemon to delete unused cache data
14
+func (cli *Client) BuildCachePrune(ctx context.Context, opts build.CachePruneOptions) (*build.CachePruneReport, error) {
15
+	if err := cli.NewVersionError(ctx, "1.31", "build prune"); err != nil {
16
+		return nil, err
17
+	}
18
+
19
+	query := url.Values{}
20
+	if opts.All {
21
+		query.Set("all", "1")
22
+	}
23
+
24
+	if opts.KeepStorage != 0 {
25
+		query.Set("keep-storage", strconv.Itoa(int(opts.KeepStorage)))
26
+	}
27
+	if opts.ReservedSpace != 0 {
28
+		query.Set("reserved-space", strconv.Itoa(int(opts.ReservedSpace)))
29
+	}
30
+	if opts.MaxUsedSpace != 0 {
31
+		query.Set("max-used-space", strconv.Itoa(int(opts.MaxUsedSpace)))
32
+	}
33
+	if opts.MinFreeSpace != 0 {
34
+		query.Set("min-free-space", strconv.Itoa(int(opts.MinFreeSpace)))
35
+	}
36
+	f, err := filters.ToJSON(opts.Filters)
37
+	if err != nil {
38
+		return nil, errors.Wrap(err, "prune could not marshal filters option")
39
+	}
40
+	query.Set("filters", f)
41
+
42
+	resp, err := cli.post(ctx, "/build/prune", query, nil, nil)
43
+	defer ensureReaderClosed(resp)
44
+
45
+	if err != nil {
46
+		return nil, err
47
+	}
48
+
49
+	report := build.CachePruneReport{}
50
+	if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
51
+		return nil, errors.Wrap(err, "error retrieving disk usage")
52
+	}
53
+
54
+	return &report, nil
55
+}
0 56
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/moby/moby/api/types/checkpoint"
6
+)
7
+
8
+// CheckpointAPIClient defines API client methods for the checkpoints.
9
+//
10
+// Experimental: checkpoint and restore is still an experimental feature,
11
+// and only available if the daemon is running with experimental features
12
+// enabled.
13
+type CheckpointAPIClient interface {
14
+	CheckpointCreate(ctx context.Context, container string, options checkpoint.CreateOptions) error
15
+	CheckpointDelete(ctx context.Context, container string, options checkpoint.DeleteOptions) error
16
+	CheckpointList(ctx context.Context, container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error)
17
+}
0 18
new file mode 100644
... ...
@@ -0,0 +1,19 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/moby/moby/api/types/checkpoint"
6
+)
7
+
8
+// CheckpointCreate creates a checkpoint from the given container with the given name
9
+func (cli *Client) CheckpointCreate(ctx context.Context, containerID string, options checkpoint.CreateOptions) error {
10
+	containerID, err := trimID("container", containerID)
11
+	if err != nil {
12
+		return err
13
+	}
14
+
15
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/checkpoints", nil, options, nil)
16
+	ensureReaderClosed(resp)
17
+	return err
18
+}
0 19
new file mode 100644
... ...
@@ -0,0 +1,25 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+
6
+	"github.com/moby/moby/api/types/checkpoint"
7
+)
8
+
9
+// CheckpointDelete deletes the checkpoint with the given name from the given container
10
+func (cli *Client) CheckpointDelete(ctx context.Context, containerID string, options checkpoint.DeleteOptions) error {
11
+	containerID, err := trimID("container", containerID)
12
+	if err != nil {
13
+		return err
14
+	}
15
+
16
+	query := url.Values{}
17
+	if options.CheckpointDir != "" {
18
+		query.Set("dir", options.CheckpointDir)
19
+	}
20
+
21
+	resp, err := cli.delete(ctx, "/containers/"+containerID+"/checkpoints/"+options.CheckpointID, query, nil)
22
+	ensureReaderClosed(resp)
23
+	return err
24
+}
0 25
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types/checkpoint"
8
+)
9
+
10
+// CheckpointList returns the checkpoints of the given container in the docker host
11
+func (cli *Client) CheckpointList(ctx context.Context, container string, options checkpoint.ListOptions) ([]checkpoint.Summary, error) {
12
+	var checkpoints []checkpoint.Summary
13
+
14
+	query := url.Values{}
15
+	if options.CheckpointDir != "" {
16
+		query.Set("dir", options.CheckpointDir)
17
+	}
18
+
19
+	resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", query, nil)
20
+	defer ensureReaderClosed(resp)
21
+	if err != nil {
22
+		return checkpoints, err
23
+	}
24
+
25
+	err = json.NewDecoder(resp.Body).Decode(&checkpoints)
26
+	return checkpoints, err
27
+}
0 28
new file mode 100644
... ...
@@ -0,0 +1,485 @@
0
+/*
1
+Package client is a Go client for the Docker Engine API.
2
+
3
+For more information about the Engine API, see the documentation:
4
+https://docs.docker.com/reference/api/engine/
5
+
6
+# Usage
7
+
8
+You use the library by constructing a client object using [NewClientWithOpts]
9
+and calling methods on it. The client can be configured from environment
10
+variables by passing the [FromEnv] option, or configured manually by passing any
11
+of the other available [Opts].
12
+
13
+For example, to list running containers (the equivalent of "docker ps"):
14
+
15
+	package main
16
+
17
+	import (
18
+		"context"
19
+		"fmt"
20
+
21
+		"github.com/moby/moby/api/types/container"
22
+		"github.com/moby/moby/client"
23
+	)
24
+
25
+	func main() {
26
+		cli, err := client.NewClientWithOpts(client.FromEnv)
27
+		if err != nil {
28
+			panic(err)
29
+		}
30
+
31
+		containers, err := cli.ContainerList(context.Background(), container.ListOptions{})
32
+		if err != nil {
33
+			panic(err)
34
+		}
35
+
36
+		for _, ctr := range containers {
37
+			fmt.Printf("%s %s\n", ctr.ID, ctr.Image)
38
+		}
39
+	}
40
+*/
41
+package client
42
+
43
+import (
44
+	"context"
45
+	"crypto/tls"
46
+	"net"
47
+	"net/http"
48
+	"net/url"
49
+	"path"
50
+	"strings"
51
+	"sync"
52
+	"sync/atomic"
53
+	"time"
54
+
55
+	"github.com/docker/go-connections/sockets"
56
+	"github.com/moby/moby/api/types"
57
+	"github.com/moby/moby/api/types/versions"
58
+	"github.com/pkg/errors"
59
+	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
60
+)
61
+
62
+// DummyHost is a hostname used for local communication.
63
+//
64
+// It acts as a valid formatted hostname for local connections (such as "unix://"
65
+// or "npipe://") which do not require a hostname. It should never be resolved,
66
+// but uses the special-purpose ".localhost" TLD (as defined in [RFC 2606, Section 2]
67
+// and [RFC 6761, Section 6.3]).
68
+//
69
+// [RFC 7230, Section 5.4] defines that an empty header must be used for such
70
+// cases:
71
+//
72
+//	If the authority component is missing or undefined for the target URI,
73
+//	then a client MUST send a Host header field with an empty field-value.
74
+//
75
+// However, [Go stdlib] enforces the semantics of HTTP(S) over TCP, does not
76
+// allow an empty header to be used, and requires req.URL.Scheme to be either
77
+// "http" or "https".
78
+//
79
+// For further details, refer to:
80
+//
81
+//   - https://github.com/docker/engine-api/issues/189
82
+//   - https://github.com/golang/go/issues/13624
83
+//   - https://github.com/golang/go/issues/61076
84
+//   - https://github.com/moby/moby/issues/45935
85
+//
86
+// [RFC 2606, Section 2]: https://www.rfc-editor.org/rfc/rfc2606.html#section-2
87
+// [RFC 6761, Section 6.3]: https://www.rfc-editor.org/rfc/rfc6761#section-6.3
88
+// [RFC 7230, Section 5.4]: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
89
+// [Go stdlib]: https://github.com/golang/go/blob/6244b1946bc2101b01955468f1be502dbadd6807/src/net/http/transport.go#L558-L569
90
+const DummyHost = "api.moby.localhost"
91
+
92
+// DefaultAPIVersion is the highest REST API version supported by the client.
93
+// If API-version negotiation is enabled (see [WithAPIVersionNegotiation],
94
+// [Client.NegotiateAPIVersion]), the client may downgrade its API version.
95
+// Similarly, the [WithVersion] and [WithVersionFromEnv] allow overriding
96
+// the version.
97
+//
98
+// This version may be lower than the [api.DefaultVersion], which is the default
99
+// (and highest supported) version of the api library module used.
100
+const DefaultAPIVersion = "1.52"
101
+
102
+// fallbackAPIVersion is the version to fallback to if API-version negotiation
103
+// fails. This version is the highest version of the API before API-version
104
+// negotiation was introduced. If negotiation fails (or no API version was
105
+// included in the API response), we assume the API server uses the most
106
+// recent version before negotiation was introduced.
107
+const fallbackAPIVersion = "1.24"
108
+
109
+// Ensure that Client always implements APIClient.
110
+var _ APIClient = &Client{}
111
+
112
+// Client is the API client that performs all operations
113
+// against a docker server.
114
+type Client struct {
115
+	// scheme sets the scheme for the client
116
+	scheme string
117
+	// host holds the server address to connect to
118
+	host string
119
+	// proto holds the client protocol i.e. unix.
120
+	proto string
121
+	// addr holds the client address.
122
+	addr string
123
+	// basePath holds the path to prepend to the requests.
124
+	basePath string
125
+	// client used to send and receive http requests.
126
+	client *http.Client
127
+	// version of the server to talk to.
128
+	version string
129
+	// userAgent is the User-Agent header to use for HTTP requests. It takes
130
+	// precedence over User-Agent headers set in customHTTPHeaders, and other
131
+	// header variables. When set to an empty string, the User-Agent header
132
+	// is removed, and no header is sent.
133
+	userAgent *string
134
+	// custom HTTP headers configured by users.
135
+	customHTTPHeaders map[string]string
136
+	// manualOverride is set to true when the version was set by users.
137
+	manualOverride bool
138
+
139
+	// negotiateVersion indicates if the client should automatically negotiate
140
+	// the API version to use when making requests. API version negotiation is
141
+	// performed on the first request, after which negotiated is set to "true"
142
+	// so that subsequent requests do not re-negotiate.
143
+	negotiateVersion bool
144
+
145
+	// negotiated indicates that API version negotiation took place
146
+	negotiated atomic.Bool
147
+
148
+	// negotiateLock is used to single-flight the version negotiation process
149
+	negotiateLock sync.Mutex
150
+
151
+	traceOpts []otelhttp.Option
152
+
153
+	// When the client transport is an *http.Transport (default) we need to do some extra things (like closing idle connections).
154
+	// Store the original transport as the http.Client transport will be wrapped with tracing libs.
155
+	baseTransport *http.Transport
156
+}
157
+
158
+// ErrRedirect is the error returned by checkRedirect when the request is non-GET.
159
+var ErrRedirect = errors.New("unexpected redirect in response")
160
+
161
+// CheckRedirect specifies the policy for dealing with redirect responses. It
162
+// can be set on [http.Client.CheckRedirect] to prevent HTTP redirects for
163
+// non-GET requests. It returns an [ErrRedirect] for non-GET request, otherwise
164
+// returns a [http.ErrUseLastResponse], which is special-cased by http.Client
165
+// to use the last response.
166
+//
167
+// Go 1.8 changed behavior for HTTP redirects (specifically 301, 307, and 308)
168
+// in the client. The client (and by extension API client) can be made to send
169
+// a request like "POST /containers//start" where what would normally be in the
170
+// name section of the URL is empty. This triggers an HTTP 301 from the daemon.
171
+//
172
+// In go 1.8 this 301 is converted to a GET request, and ends up getting
173
+// a 404 from the daemon. This behavior change manifests in the client in that
174
+// before, the 301 was not followed and the client did not generate an error,
175
+// but now results in a message like "Error response from daemon: page not found".
176
+func CheckRedirect(_ *http.Request, via []*http.Request) error {
177
+	if via[0].Method == http.MethodGet {
178
+		return http.ErrUseLastResponse
179
+	}
180
+	return ErrRedirect
181
+}
182
+
183
+// NewClientWithOpts initializes a new API client with a default HTTPClient, and
184
+// default API host and version. It also initializes the custom HTTP headers to
185
+// add to each request.
186
+//
187
+// It takes an optional list of [Opt] functional arguments, which are applied in
188
+// the order they're provided, which allows modifying the defaults when creating
189
+// the client. For example, the following initializes a client that configures
190
+// itself with values from environment variables ([FromEnv]), and has automatic
191
+// API version negotiation enabled ([WithAPIVersionNegotiation]).
192
+//
193
+//	cli, err := client.NewClientWithOpts(
194
+//		client.FromEnv,
195
+//		client.WithAPIVersionNegotiation(),
196
+//	)
197
+func NewClientWithOpts(ops ...Opt) (*Client, error) {
198
+	hostURL, err := ParseHostURL(DefaultDockerHost)
199
+	if err != nil {
200
+		return nil, err
201
+	}
202
+
203
+	client, err := defaultHTTPClient(hostURL)
204
+	if err != nil {
205
+		return nil, err
206
+	}
207
+	c := &Client{
208
+		host:    DefaultDockerHost,
209
+		version: DefaultAPIVersion,
210
+		client:  client,
211
+		proto:   hostURL.Scheme,
212
+		addr:    hostURL.Host,
213
+
214
+		traceOpts: []otelhttp.Option{
215
+			otelhttp.WithSpanNameFormatter(func(_ string, req *http.Request) string {
216
+				return req.Method + " " + req.URL.Path
217
+			}),
218
+		},
219
+	}
220
+
221
+	for _, op := range ops {
222
+		if err := op(c); err != nil {
223
+			return nil, err
224
+		}
225
+	}
226
+
227
+	if tr, ok := c.client.Transport.(*http.Transport); ok {
228
+		// Store the base transport before we wrap it in tracing libs below
229
+		// This is used, as an example, to close idle connections when the client is closed
230
+		c.baseTransport = tr
231
+	}
232
+
233
+	if c.scheme == "" {
234
+		// TODO(stevvooe): This isn't really the right way to write clients in Go.
235
+		// `NewClient` should probably only take an `*http.Client` and work from there.
236
+		// Unfortunately, the model of having a host-ish/url-thingy as the connection
237
+		// string has us confusing protocol and transport layers. We continue doing
238
+		// this to avoid breaking existing clients but this should be addressed.
239
+		if c.tlsConfig() != nil {
240
+			c.scheme = "https"
241
+		} else {
242
+			c.scheme = "http"
243
+		}
244
+	}
245
+
246
+	c.client.Transport = otelhttp.NewTransport(c.client.Transport, c.traceOpts...)
247
+
248
+	return c, nil
249
+}
250
+
251
+func (cli *Client) tlsConfig() *tls.Config {
252
+	if cli.baseTransport == nil {
253
+		return nil
254
+	}
255
+	return cli.baseTransport.TLSClientConfig
256
+}
257
+
258
+func defaultHTTPClient(hostURL *url.URL) (*http.Client, error) {
259
+	transport := &http.Transport{}
260
+	// Necessary to prevent long-lived processes using the
261
+	// client from leaking connections due to idle connections
262
+	// not being released.
263
+	// TODO: see if we can also address this from the server side,
264
+	// or in go-connections.
265
+	// see: https://github.com/moby/moby/issues/45539
266
+	transport.MaxIdleConns = 6
267
+	transport.IdleConnTimeout = 30 * time.Second
268
+	err := sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
269
+	if err != nil {
270
+		return nil, err
271
+	}
272
+	return &http.Client{
273
+		Transport:     transport,
274
+		CheckRedirect: CheckRedirect,
275
+	}, nil
276
+}
277
+
278
+// Close the transport used by the client
279
+func (cli *Client) Close() error {
280
+	if cli.baseTransport != nil {
281
+		cli.baseTransport.CloseIdleConnections()
282
+		return nil
283
+	}
284
+	return nil
285
+}
286
+
287
+// checkVersion manually triggers API version negotiation (if configured).
288
+// This allows for version-dependent code to use the same version as will
289
+// be negotiated when making the actual requests, and for which cases
290
+// we cannot do the negotiation lazily.
291
+func (cli *Client) checkVersion(ctx context.Context) error {
292
+	if !cli.manualOverride && cli.negotiateVersion && !cli.negotiated.Load() {
293
+		// Ensure exclusive write access to version and negotiated fields
294
+		cli.negotiateLock.Lock()
295
+		defer cli.negotiateLock.Unlock()
296
+
297
+		// May have been set during last execution of critical zone
298
+		if cli.negotiated.Load() {
299
+			return nil
300
+		}
301
+
302
+		ping, err := cli.Ping(ctx)
303
+		if err != nil {
304
+			return err
305
+		}
306
+		cli.negotiateAPIVersionPing(ping)
307
+	}
308
+	return nil
309
+}
310
+
311
+// getAPIPath returns the versioned request path to call the API.
312
+// It appends the query parameters to the path if they are not empty.
313
+func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) string {
314
+	var apiPath string
315
+	_ = cli.checkVersion(ctx)
316
+	if cli.version != "" {
317
+		apiPath = path.Join(cli.basePath, "/v"+strings.TrimPrefix(cli.version, "v"), p)
318
+	} else {
319
+		apiPath = path.Join(cli.basePath, p)
320
+	}
321
+	return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String()
322
+}
323
+
324
+// ClientVersion returns the API version used by this client.
325
+func (cli *Client) ClientVersion() string {
326
+	return cli.version
327
+}
328
+
329
+// NegotiateAPIVersion queries the API and updates the version to match the API
330
+// version. NegotiateAPIVersion downgrades the client's API version to match the
331
+// APIVersion if the ping version is lower than the default version. If the API
332
+// version reported by the server is higher than the maximum version supported
333
+// by the client, it uses the client's maximum version.
334
+//
335
+// If a manual override is in place, either through the "DOCKER_API_VERSION"
336
+// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
337
+// with a fixed version ([WithVersion]), no negotiation is performed.
338
+//
339
+// If the API server's ping response does not contain an API version, or if the
340
+// client did not get a successful ping response, it assumes it is connected with
341
+// an old daemon that does not support API version negotiation, in which case it
342
+// downgrades to the latest version of the API before version negotiation was
343
+// added (1.24).
344
+func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
345
+	if !cli.manualOverride {
346
+		// Avoid concurrent modification of version-related fields
347
+		cli.negotiateLock.Lock()
348
+		defer cli.negotiateLock.Unlock()
349
+
350
+		ping, err := cli.Ping(ctx)
351
+		if err != nil {
352
+			// FIXME(thaJeztah): Ping returns an error when failing to connect to the API; we should not swallow the error here, and instead returning it.
353
+			return
354
+		}
355
+		cli.negotiateAPIVersionPing(ping)
356
+	}
357
+}
358
+
359
+// NegotiateAPIVersionPing downgrades the client's API version to match the
360
+// APIVersion in the ping response. If the API version in pingResponse is higher
361
+// than the maximum version supported by the client, it uses the client's maximum
362
+// version.
363
+//
364
+// If a manual override is in place, either through the "DOCKER_API_VERSION"
365
+// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
366
+// with a fixed version ([WithVersion]), no negotiation is performed.
367
+//
368
+// If the API server's ping response does not contain an API version, we assume
369
+// we are connected with an old daemon without API version negotiation support,
370
+// and downgrade to the latest version of the API before version negotiation was
371
+// added (1.24).
372
+func (cli *Client) NegotiateAPIVersionPing(pingResponse types.Ping) {
373
+	if !cli.manualOverride {
374
+		// Avoid concurrent modification of version-related fields
375
+		cli.negotiateLock.Lock()
376
+		defer cli.negotiateLock.Unlock()
377
+
378
+		cli.negotiateAPIVersionPing(pingResponse)
379
+	}
380
+}
381
+
382
+// negotiateAPIVersionPing queries the API and updates the version to match the
383
+// API version from the ping response.
384
+func (cli *Client) negotiateAPIVersionPing(pingResponse types.Ping) {
385
+	// default to the latest version before versioning headers existed
386
+	if pingResponse.APIVersion == "" {
387
+		pingResponse.APIVersion = fallbackAPIVersion
388
+	}
389
+
390
+	// if the client is not initialized with a version, start with the latest supported version
391
+	if cli.version == "" {
392
+		cli.version = DefaultAPIVersion
393
+	}
394
+
395
+	// if server version is lower than the client version, downgrade
396
+	if versions.LessThan(pingResponse.APIVersion, cli.version) {
397
+		cli.version = pingResponse.APIVersion
398
+	}
399
+
400
+	// Store the results, so that automatic API version negotiation (if enabled)
401
+	// won't be performed on the next request.
402
+	if cli.negotiateVersion {
403
+		cli.negotiated.Store(true)
404
+	}
405
+}
406
+
407
+// DaemonHost returns the host address used by the client
408
+func (cli *Client) DaemonHost() string {
409
+	return cli.host
410
+}
411
+
412
+// HTTPClient returns a copy of the HTTP client bound to the server
413
+func (cli *Client) HTTPClient() *http.Client {
414
+	c := *cli.client
415
+	return &c
416
+}
417
+
418
+// ParseHostURL parses a url string, validates the string is a host url, and
419
+// returns the parsed URL
420
+func ParseHostURL(host string) (*url.URL, error) {
421
+	proto, addr, ok := strings.Cut(host, "://")
422
+	if !ok || addr == "" {
423
+		return nil, errors.Errorf("unable to parse docker host `%s`", host)
424
+	}
425
+
426
+	var basePath string
427
+	if proto == "tcp" {
428
+		parsed, err := url.Parse("tcp://" + addr)
429
+		if err != nil {
430
+			return nil, err
431
+		}
432
+		addr = parsed.Host
433
+		basePath = parsed.Path
434
+	}
435
+	return &url.URL{
436
+		Scheme: proto,
437
+		Host:   addr,
438
+		Path:   basePath,
439
+	}, nil
440
+}
441
+
442
+func (cli *Client) dialerFromTransport() func(context.Context, string, string) (net.Conn, error) {
443
+	if cli.baseTransport == nil || cli.baseTransport.DialContext == nil {
444
+		return nil
445
+	}
446
+
447
+	if cli.baseTransport.TLSClientConfig != nil {
448
+		// When using a tls config we don't use the configured dialer but instead a fallback dialer...
449
+		// Note: It seems like this should use the normal dialer and wrap the returned net.Conn in a tls.Conn
450
+		// I honestly don't know why it doesn't do that, but it doesn't and such a change is entirely unrelated to the change in this commit.
451
+		return nil
452
+	}
453
+	return cli.baseTransport.DialContext
454
+}
455
+
456
+// Dialer returns a dialer for a raw stream connection, with an HTTP/1.1 header,
457
+// that can be used for proxying the daemon connection. It is used by
458
+// ["docker dial-stdio"].
459
+//
460
+// ["docker dial-stdio"]: https://github.com/docker/cli/pull/1014
461
+func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
462
+	return cli.dialer()
463
+}
464
+
465
+func (cli *Client) dialer() func(context.Context) (net.Conn, error) {
466
+	return func(ctx context.Context) (net.Conn, error) {
467
+		if dialFn := cli.dialerFromTransport(); dialFn != nil {
468
+			return dialFn(ctx, cli.proto, cli.addr)
469
+		}
470
+		switch cli.proto {
471
+		case "unix":
472
+			return net.Dial(cli.proto, cli.addr)
473
+		case "npipe":
474
+			ctx, cancel := context.WithTimeout(ctx, 32*time.Second)
475
+			defer cancel()
476
+			return dialPipeContext(ctx, cli.addr)
477
+		default:
478
+			if tlsConfig := cli.tlsConfig(); tlsConfig != nil {
479
+				return tls.Dial(cli.proto, cli.addr, tlsConfig)
480
+			}
481
+			return net.Dial(cli.proto, cli.addr)
482
+		}
483
+	}
484
+}
0 485
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+package client
1
+
2
+import "net/http"
3
+
4
+// NewClient initializes a new API client for the given host and API version.
5
+// It uses the given http client as transport.
6
+// It also initializes the custom http headers to add to each request.
7
+//
8
+// It won't send any version information if the version number is empty. It is
9
+// highly recommended that you set a version or your client may break if the
10
+// server is upgraded.
11
+//
12
+// Deprecated: use [NewClientWithOpts] passing the [WithHost], [WithVersion],
13
+// [WithHTTPClient] and [WithHTTPHeaders] options. We recommend enabling API
14
+// version negotiation by passing the [WithAPIVersionNegotiation] option instead
15
+// of WithVersion.
16
+func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
17
+	return NewClientWithOpts(WithHost(host), WithVersion(version), WithHTTPClient(client), WithHTTPHeaders(httpHeaders))
18
+}
19
+
20
+// NewEnvClient initializes a new API client based on environment variables.
21
+// See FromEnv for a list of support environment variables.
22
+//
23
+// Deprecated: use [NewClientWithOpts] passing the [FromEnv] option.
24
+func NewEnvClient() (*Client, error) {
25
+	return NewClientWithOpts(FromEnv)
26
+}
0 27
new file mode 100644
... ...
@@ -0,0 +1,237 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"net"
6
+	"net/http"
7
+
8
+	"github.com/moby/moby/api/types"
9
+	"github.com/moby/moby/api/types/build"
10
+	"github.com/moby/moby/api/types/container"
11
+	"github.com/moby/moby/api/types/events"
12
+	"github.com/moby/moby/api/types/filters"
13
+	"github.com/moby/moby/api/types/image"
14
+	"github.com/moby/moby/api/types/network"
15
+	"github.com/moby/moby/api/types/registry"
16
+	"github.com/moby/moby/api/types/swarm"
17
+	"github.com/moby/moby/api/types/system"
18
+	"github.com/moby/moby/api/types/volume"
19
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
20
+)
21
+
22
+// CommonAPIClient is the common methods between stable and experimental versions of APIClient.
23
+//
24
+// Deprecated: use [APIClient] instead. This type will be an alias for [APIClient] in the next release, and removed after.
25
+type CommonAPIClient = stableAPIClient
26
+
27
+// APIClient is an interface that clients that talk with a docker server must implement.
28
+type APIClient interface {
29
+	stableAPIClient
30
+	CheckpointAPIClient // CheckpointAPIClient is still experimental.
31
+}
32
+
33
+type stableAPIClient interface {
34
+	ConfigAPIClient
35
+	ContainerAPIClient
36
+	DistributionAPIClient
37
+	ImageAPIClient
38
+	NetworkAPIClient
39
+	PluginAPIClient
40
+	SystemAPIClient
41
+	VolumeAPIClient
42
+	ClientVersion() string
43
+	DaemonHost() string
44
+	HTTPClient() *http.Client
45
+	ServerVersion(ctx context.Context) (types.Version, error)
46
+	NegotiateAPIVersion(ctx context.Context)
47
+	NegotiateAPIVersionPing(types.Ping)
48
+	HijackDialer
49
+	Dialer() func(context.Context) (net.Conn, error)
50
+	Close() error
51
+	SwarmManagementAPIClient
52
+}
53
+
54
+// SwarmManagementAPIClient defines all methods for managing Swarm-specific
55
+// objects.
56
+type SwarmManagementAPIClient interface {
57
+	SwarmAPIClient
58
+	NodeAPIClient
59
+	ServiceAPIClient
60
+	SecretAPIClient
61
+	ConfigAPIClient
62
+}
63
+
64
+// HijackDialer defines methods for a hijack dialer.
65
+type HijackDialer interface {
66
+	DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error)
67
+}
68
+
69
+// ContainerAPIClient defines API client methods for the containers
70
+type ContainerAPIClient interface {
71
+	ContainerAttach(ctx context.Context, container string, options container.AttachOptions) (types.HijackedResponse, error)
72
+	ContainerCommit(ctx context.Context, container string, options container.CommitOptions) (container.CommitResponse, error)
73
+	ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
74
+	ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error)
75
+	ContainerExecAttach(ctx context.Context, execID string, options container.ExecAttachOptions) (types.HijackedResponse, error)
76
+	ContainerExecCreate(ctx context.Context, container string, options container.ExecOptions) (container.ExecCreateResponse, error)
77
+	ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error)
78
+	ContainerExecResize(ctx context.Context, execID string, options container.ResizeOptions) error
79
+	ContainerExecStart(ctx context.Context, execID string, options container.ExecStartOptions) error
80
+	ContainerExport(ctx context.Context, container string) (io.ReadCloser, error)
81
+	ContainerInspect(ctx context.Context, container string) (container.InspectResponse, error)
82
+	ContainerInspectWithRaw(ctx context.Context, container string, getSize bool) (container.InspectResponse, []byte, error)
83
+	ContainerKill(ctx context.Context, container, signal string) error
84
+	ContainerList(ctx context.Context, options container.ListOptions) ([]container.Summary, error)
85
+	ContainerLogs(ctx context.Context, container string, options container.LogsOptions) (io.ReadCloser, error)
86
+	ContainerPause(ctx context.Context, container string) error
87
+	ContainerRemove(ctx context.Context, container string, options container.RemoveOptions) error
88
+	ContainerRename(ctx context.Context, container, newContainerName string) error
89
+	ContainerResize(ctx context.Context, container string, options container.ResizeOptions) error
90
+	ContainerRestart(ctx context.Context, container string, options container.StopOptions) error
91
+	ContainerStatPath(ctx context.Context, container, path string) (container.PathStat, error)
92
+	ContainerStats(ctx context.Context, container string, stream bool) (container.StatsResponseReader, error)
93
+	ContainerStatsOneShot(ctx context.Context, container string) (container.StatsResponseReader, error)
94
+	ContainerStart(ctx context.Context, container string, options container.StartOptions) error
95
+	ContainerStop(ctx context.Context, container string, options container.StopOptions) error
96
+	ContainerTop(ctx context.Context, container string, arguments []string) (container.TopResponse, error)
97
+	ContainerUnpause(ctx context.Context, container string) error
98
+	ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.UpdateResponse, error)
99
+	ContainerWait(ctx context.Context, container string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error)
100
+	CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, container.PathStat, error)
101
+	CopyToContainer(ctx context.Context, container, path string, content io.Reader, options container.CopyToContainerOptions) error
102
+	ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error)
103
+}
104
+
105
+// DistributionAPIClient defines API client methods for the registry
106
+type DistributionAPIClient interface {
107
+	DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registry.DistributionInspect, error)
108
+}
109
+
110
+// ImageAPIClient defines API client methods for the images
111
+type ImageAPIClient interface {
112
+	ImageBuild(ctx context.Context, context io.Reader, options build.ImageBuildOptions) (build.ImageBuildResponse, error)
113
+	BuildCachePrune(ctx context.Context, opts build.CachePruneOptions) (*build.CachePruneReport, error)
114
+	BuildCancel(ctx context.Context, id string) error
115
+	ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error)
116
+	ImageImport(ctx context.Context, source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error)
117
+
118
+	ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error)
119
+	ImagePull(ctx context.Context, ref string, options image.PullOptions) (io.ReadCloser, error)
120
+	ImagePush(ctx context.Context, ref string, options image.PushOptions) (io.ReadCloser, error)
121
+	ImageRemove(ctx context.Context, image string, options image.RemoveOptions) ([]image.DeleteResponse, error)
122
+	ImageSearch(ctx context.Context, term string, options registry.SearchOptions) ([]registry.SearchResult, error)
123
+	ImageTag(ctx context.Context, image, ref string) error
124
+	ImagesPrune(ctx context.Context, pruneFilter filters.Args) (image.PruneReport, error)
125
+
126
+	ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (image.InspectResponse, error)
127
+	ImageHistory(ctx context.Context, image string, _ ...ImageHistoryOption) ([]image.HistoryResponseItem, error)
128
+	ImageLoad(ctx context.Context, input io.Reader, _ ...ImageLoadOption) (image.LoadResponse, error)
129
+	ImageSave(ctx context.Context, images []string, _ ...ImageSaveOption) (io.ReadCloser, error)
130
+
131
+	ImageAPIClientDeprecated
132
+}
133
+
134
+// ImageAPIClientDeprecated defines deprecated methods of the ImageAPIClient.
135
+type ImageAPIClientDeprecated interface {
136
+	// ImageInspectWithRaw returns the image information and its raw representation.
137
+	//
138
+	// Deprecated: Use [Client.ImageInspect] instead. Raw response can be obtained using the [ImageInspectWithRawResponse] option.
139
+	ImageInspectWithRaw(ctx context.Context, image string) (image.InspectResponse, []byte, error)
140
+}
141
+
142
+// NetworkAPIClient defines API client methods for the networks
143
+type NetworkAPIClient interface {
144
+	NetworkConnect(ctx context.Context, network, container string, config *network.EndpointSettings) error
145
+	NetworkCreate(ctx context.Context, name string, options network.CreateOptions) (network.CreateResponse, error)
146
+	NetworkDisconnect(ctx context.Context, network, container string, force bool) error
147
+	NetworkInspect(ctx context.Context, network string, options network.InspectOptions) (network.Inspect, error)
148
+	NetworkInspectWithRaw(ctx context.Context, network string, options network.InspectOptions) (network.Inspect, []byte, error)
149
+	NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error)
150
+	NetworkRemove(ctx context.Context, network string) error
151
+	NetworksPrune(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error)
152
+}
153
+
154
+// NodeAPIClient defines API client methods for the nodes
155
+type NodeAPIClient interface {
156
+	NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error)
157
+	NodeList(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error)
158
+	NodeRemove(ctx context.Context, nodeID string, options swarm.NodeRemoveOptions) error
159
+	NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error
160
+}
161
+
162
+// PluginAPIClient defines API client methods for the plugins
163
+type PluginAPIClient interface {
164
+	PluginList(ctx context.Context, filter filters.Args) (types.PluginsListResponse, error)
165
+	PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error
166
+	PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error
167
+	PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error
168
+	PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) (io.ReadCloser, error)
169
+	PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (io.ReadCloser, error)
170
+	PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error)
171
+	PluginSet(ctx context.Context, name string, args []string) error
172
+	PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error)
173
+	PluginCreate(ctx context.Context, createContext io.Reader, options types.PluginCreateOptions) error
174
+}
175
+
176
+// ServiceAPIClient defines API client methods for the services
177
+type ServiceAPIClient interface {
178
+	ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options swarm.ServiceCreateOptions) (swarm.ServiceCreateResponse, error)
179
+	ServiceInspectWithRaw(ctx context.Context, serviceID string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error)
180
+	ServiceList(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error)
181
+	ServiceRemove(ctx context.Context, serviceID string) error
182
+	ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
183
+	ServiceLogs(ctx context.Context, serviceID string, options container.LogsOptions) (io.ReadCloser, error)
184
+	TaskLogs(ctx context.Context, taskID string, options container.LogsOptions) (io.ReadCloser, error)
185
+	TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error)
186
+	TaskList(ctx context.Context, options swarm.TaskListOptions) ([]swarm.Task, error)
187
+}
188
+
189
+// SwarmAPIClient defines API client methods for the swarm
190
+type SwarmAPIClient interface {
191
+	SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error)
192
+	SwarmJoin(ctx context.Context, req swarm.JoinRequest) error
193
+	SwarmGetUnlockKey(ctx context.Context) (swarm.UnlockKeyResponse, error)
194
+	SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error
195
+	SwarmLeave(ctx context.Context, force bool) error
196
+	SwarmInspect(ctx context.Context) (swarm.Swarm, error)
197
+	SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags swarm.UpdateFlags) error
198
+}
199
+
200
+// SystemAPIClient defines API client methods for the system
201
+type SystemAPIClient interface {
202
+	Events(ctx context.Context, options events.ListOptions) (<-chan events.Message, <-chan error)
203
+	Info(ctx context.Context) (system.Info, error)
204
+	RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error)
205
+	DiskUsage(ctx context.Context, options types.DiskUsageOptions) (types.DiskUsage, error)
206
+	Ping(ctx context.Context) (types.Ping, error)
207
+}
208
+
209
+// VolumeAPIClient defines API client methods for the volumes
210
+type VolumeAPIClient interface {
211
+	VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error)
212
+	VolumeInspect(ctx context.Context, volumeID string) (volume.Volume, error)
213
+	VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error)
214
+	VolumeList(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error)
215
+	VolumeRemove(ctx context.Context, volumeID string, force bool) error
216
+	VolumesPrune(ctx context.Context, pruneFilter filters.Args) (volume.PruneReport, error)
217
+	VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error
218
+}
219
+
220
+// SecretAPIClient defines API client methods for secrets
221
+type SecretAPIClient interface {
222
+	SecretList(ctx context.Context, options swarm.SecretListOptions) ([]swarm.Secret, error)
223
+	SecretCreate(ctx context.Context, secret swarm.SecretSpec) (swarm.SecretCreateResponse, error)
224
+	SecretRemove(ctx context.Context, id string) error
225
+	SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error)
226
+	SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error
227
+}
228
+
229
+// ConfigAPIClient defines API client methods for configs
230
+type ConfigAPIClient interface {
231
+	ConfigList(ctx context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error)
232
+	ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (swarm.ConfigCreateResponse, error)
233
+	ConfigRemove(ctx context.Context, id string) error
234
+	ConfigInspectWithRaw(ctx context.Context, name string) (swarm.Config, []byte, error)
235
+	ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error
236
+}
0 237
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+//go:build !windows
1
+
2
+package client
3
+
4
+import (
5
+	"context"
6
+	"net"
7
+	"syscall"
8
+)
9
+
10
+// DefaultDockerHost defines OS-specific default host if the DOCKER_HOST
11
+// (EnvOverrideHost) environment variable is unset or empty.
12
+const DefaultDockerHost = "unix:///var/run/docker.sock"
13
+
14
+// dialPipeContext connects to a Windows named pipe. It is not supported on non-Windows.
15
+func dialPipeContext(_ context.Context, _ string) (net.Conn, error) {
16
+	return nil, syscall.EAFNOSUPPORT
17
+}
0 18
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net"
5
+
6
+	"github.com/Microsoft/go-winio"
7
+)
8
+
9
+// DefaultDockerHost defines OS-specific default host if the DOCKER_HOST
10
+// (EnvOverrideHost) environment variable is unset or empty.
11
+const DefaultDockerHost = "npipe:////./pipe/docker_engine"
12
+
13
+// dialPipeContext connects to a Windows named pipe. It is not supported on non-Windows.
14
+func dialPipeContext(ctx context.Context, addr string) (net.Conn, error) {
15
+	return winio.DialPipeContext(ctx, addr)
16
+}
0 17
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+
6
+	"github.com/moby/moby/api/types/swarm"
7
+)
8
+
9
+// ConfigCreate creates a new config.
10
+func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (swarm.ConfigCreateResponse, error) {
11
+	var response swarm.ConfigCreateResponse
12
+	if err := cli.NewVersionError(ctx, "1.30", "config create"); err != nil {
13
+		return response, err
14
+	}
15
+	resp, err := cli.post(ctx, "/configs/create", nil, config, nil)
16
+	defer ensureReaderClosed(resp)
17
+	if err != nil {
18
+		return response, err
19
+	}
20
+
21
+	err = json.NewDecoder(resp.Body).Decode(&response)
22
+	return response, err
23
+}
0 24
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"context"
5
+	"encoding/json"
6
+	"io"
7
+
8
+	"github.com/moby/moby/api/types/swarm"
9
+)
10
+
11
+// ConfigInspectWithRaw returns the config information with raw data
12
+func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) {
13
+	id, err := trimID("contig", id)
14
+	if err != nil {
15
+		return swarm.Config{}, nil, err
16
+	}
17
+	if err := cli.NewVersionError(ctx, "1.30", "config inspect"); err != nil {
18
+		return swarm.Config{}, nil, err
19
+	}
20
+	resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
21
+	defer ensureReaderClosed(resp)
22
+	if err != nil {
23
+		return swarm.Config{}, nil, err
24
+	}
25
+
26
+	body, err := io.ReadAll(resp.Body)
27
+	if err != nil {
28
+		return swarm.Config{}, nil, err
29
+	}
30
+
31
+	var config swarm.Config
32
+	rdr := bytes.NewReader(body)
33
+	err = json.NewDecoder(rdr).Decode(&config)
34
+
35
+	return config, body, err
36
+}
0 37
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types/filters"
8
+	"github.com/moby/moby/api/types/swarm"
9
+)
10
+
11
+// ConfigList returns the list of configs.
12
+func (cli *Client) ConfigList(ctx context.Context, options swarm.ConfigListOptions) ([]swarm.Config, error) {
13
+	if err := cli.NewVersionError(ctx, "1.30", "config list"); err != nil {
14
+		return nil, err
15
+	}
16
+	query := url.Values{}
17
+
18
+	if options.Filters.Len() > 0 {
19
+		filterJSON, err := filters.ToJSON(options.Filters)
20
+		if err != nil {
21
+			return nil, err
22
+		}
23
+
24
+		query.Set("filters", filterJSON)
25
+	}
26
+
27
+	resp, err := cli.get(ctx, "/configs", query, nil)
28
+	defer ensureReaderClosed(resp)
29
+	if err != nil {
30
+		return nil, err
31
+	}
32
+
33
+	var configs []swarm.Config
34
+	err = json.NewDecoder(resp.Body).Decode(&configs)
35
+	return configs, err
36
+}
0 37
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+package client
1
+
2
+import "context"
3
+
4
+// ConfigRemove removes a config.
5
+func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
6
+	id, err := trimID("config", id)
7
+	if err != nil {
8
+		return err
9
+	}
10
+	if err := cli.NewVersionError(ctx, "1.30", "config remove"); err != nil {
11
+		return err
12
+	}
13
+	resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
14
+	defer ensureReaderClosed(resp)
15
+	return err
16
+}
0 17
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+
6
+	"github.com/moby/moby/api/types/swarm"
7
+)
8
+
9
+// ConfigUpdate attempts to update a config
10
+func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error {
11
+	id, err := trimID("config", id)
12
+	if err != nil {
13
+		return err
14
+	}
15
+	if err := cli.NewVersionError(ctx, "1.30", "config update"); err != nil {
16
+		return err
17
+	}
18
+	query := url.Values{}
19
+	query.Set("version", version.String())
20
+	resp, err := cli.post(ctx, "/configs/"+id+"/update", query, config, nil)
21
+	ensureReaderClosed(resp)
22
+	return err
23
+}
0 24
new file mode 100644
... ...
@@ -0,0 +1,65 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/http"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types"
8
+	"github.com/moby/moby/api/types/container"
9
+)
10
+
11
+// ContainerAttach attaches a connection to a container in the server.
12
+// It returns a types.HijackedConnection with the hijacked connection
13
+// and the a reader to get output. It's up to the called to close
14
+// the hijacked connection by calling types.HijackedResponse.Close.
15
+//
16
+// The stream format on the response will be in one of two formats:
17
+//
18
+// If the container is using a TTY, there is only a single stream (stdout), and
19
+// data is copied directly from the container output stream, no extra
20
+// multiplexing or headers.
21
+//
22
+// If the container is *not* using a TTY, streams for stdout and stderr are
23
+// multiplexed.
24
+// The format of the multiplexed stream is as follows:
25
+//
26
+//	[8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}[]byte{OUTPUT}
27
+//
28
+// STREAM_TYPE can be 1 for stdout and 2 for stderr
29
+//
30
+// SIZE1, SIZE2, SIZE3, and SIZE4 are four bytes of uint32 encoded as big endian.
31
+// This is the size of OUTPUT.
32
+//
33
+// You can use github.com/docker/docker/pkg/stdcopy.StdCopy to demultiplex this
34
+// stream.
35
+func (cli *Client) ContainerAttach(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
36
+	containerID, err := trimID("container", containerID)
37
+	if err != nil {
38
+		return types.HijackedResponse{}, err
39
+	}
40
+
41
+	query := url.Values{}
42
+	if options.Stream {
43
+		query.Set("stream", "1")
44
+	}
45
+	if options.Stdin {
46
+		query.Set("stdin", "1")
47
+	}
48
+	if options.Stdout {
49
+		query.Set("stdout", "1")
50
+	}
51
+	if options.Stderr {
52
+		query.Set("stderr", "1")
53
+	}
54
+	if options.DetachKeys != "" {
55
+		query.Set("detachKeys", options.DetachKeys)
56
+	}
57
+	if options.Logs {
58
+		query.Set("logs", "1")
59
+	}
60
+
61
+	return cli.postHijacked(ctx, "/containers/"+containerID+"/attach", query, nil, http.Header{
62
+		"Content-Type": {"text/plain"},
63
+	})
64
+}
0 65
new file mode 100644
... ...
@@ -0,0 +1,60 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"errors"
6
+	"net/url"
7
+
8
+	"github.com/distribution/reference"
9
+	"github.com/moby/moby/api/types/container"
10
+)
11
+
12
+// ContainerCommit applies changes to a container and creates a new tagged image.
13
+func (cli *Client) ContainerCommit(ctx context.Context, containerID string, options container.CommitOptions) (container.CommitResponse, error) {
14
+	containerID, err := trimID("container", containerID)
15
+	if err != nil {
16
+		return container.CommitResponse{}, err
17
+	}
18
+
19
+	var repository, tag string
20
+	if options.Reference != "" {
21
+		ref, err := reference.ParseNormalizedNamed(options.Reference)
22
+		if err != nil {
23
+			return container.CommitResponse{}, err
24
+		}
25
+
26
+		if _, isCanonical := ref.(reference.Canonical); isCanonical {
27
+			return container.CommitResponse{}, errors.New("refusing to create a tag with a digest reference")
28
+		}
29
+		ref = reference.TagNameOnly(ref)
30
+
31
+		if tagged, ok := ref.(reference.Tagged); ok {
32
+			tag = tagged.Tag()
33
+		}
34
+		repository = ref.Name()
35
+	}
36
+
37
+	query := url.Values{}
38
+	query.Set("container", containerID)
39
+	query.Set("repo", repository)
40
+	query.Set("tag", tag)
41
+	query.Set("comment", options.Comment)
42
+	query.Set("author", options.Author)
43
+	for _, change := range options.Changes {
44
+		query.Add("changes", change)
45
+	}
46
+	if !options.Pause {
47
+		query.Set("pause", "0")
48
+	}
49
+
50
+	var response container.CommitResponse
51
+	resp, err := cli.post(ctx, "/commit", query, options.Config, nil)
52
+	defer ensureReaderClosed(resp)
53
+	if err != nil {
54
+		return response, err
55
+	}
56
+
57
+	err = json.NewDecoder(resp.Body).Decode(&response)
58
+	return response, err
59
+}
0 60
new file mode 100644
... ...
@@ -0,0 +1,104 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/base64"
5
+	"encoding/json"
6
+	"fmt"
7
+	"io"
8
+	"net/http"
9
+	"net/url"
10
+	"path/filepath"
11
+	"strings"
12
+
13
+	"github.com/moby/moby/api/types/container"
14
+)
15
+
16
+// ContainerStatPath returns stat information about a path inside the container filesystem.
17
+func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (container.PathStat, error) {
18
+	containerID, err := trimID("container", containerID)
19
+	if err != nil {
20
+		return container.PathStat{}, err
21
+	}
22
+
23
+	query := url.Values{}
24
+	query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
25
+
26
+	resp, err := cli.head(ctx, "/containers/"+containerID+"/archive", query, nil)
27
+	defer ensureReaderClosed(resp)
28
+	if err != nil {
29
+		return container.PathStat{}, err
30
+	}
31
+	return getContainerPathStatFromHeader(resp.Header)
32
+}
33
+
34
+// CopyToContainer copies content into the container filesystem.
35
+// Note that `content` must be a Reader for a TAR archive
36
+func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options container.CopyToContainerOptions) error {
37
+	containerID, err := trimID("container", containerID)
38
+	if err != nil {
39
+		return err
40
+	}
41
+
42
+	query := url.Values{}
43
+	query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API.
44
+	// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
45
+	if !options.AllowOverwriteDirWithFile {
46
+		query.Set("noOverwriteDirNonDir", "true")
47
+	}
48
+
49
+	if options.CopyUIDGID {
50
+		query.Set("copyUIDGID", "true")
51
+	}
52
+
53
+	response, err := cli.putRaw(ctx, "/containers/"+containerID+"/archive", query, content, nil)
54
+	defer ensureReaderClosed(response)
55
+	if err != nil {
56
+		return err
57
+	}
58
+
59
+	return nil
60
+}
61
+
62
+// CopyFromContainer gets the content from the container and returns it as a Reader
63
+// for a TAR archive to manipulate it in the host. It's up to the caller to close the reader.
64
+func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, container.PathStat, error) {
65
+	containerID, err := trimID("container", containerID)
66
+	if err != nil {
67
+		return nil, container.PathStat{}, err
68
+	}
69
+
70
+	query := make(url.Values, 1)
71
+	query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
72
+
73
+	resp, err := cli.get(ctx, "/containers/"+containerID+"/archive", query, nil)
74
+	if err != nil {
75
+		return nil, container.PathStat{}, err
76
+	}
77
+
78
+	// In order to get the copy behavior right, we need to know information
79
+	// about both the source and the destination. The response headers include
80
+	// stat info about the source that we can use in deciding exactly how to
81
+	// copy it locally. Along with the stat info about the local destination,
82
+	// we have everything we need to handle the multiple possibilities there
83
+	// can be when copying a file/dir from one location to another file/dir.
84
+	stat, err := getContainerPathStatFromHeader(resp.Header)
85
+	if err != nil {
86
+		return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err)
87
+	}
88
+	return resp.Body, stat, err
89
+}
90
+
91
+func getContainerPathStatFromHeader(header http.Header) (container.PathStat, error) {
92
+	var stat container.PathStat
93
+
94
+	encodedStat := header.Get("X-Docker-Container-Path-Stat")
95
+	statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
96
+
97
+	err := json.NewDecoder(statDecoder).Decode(&stat)
98
+	if err != nil {
99
+		err = fmt.Errorf("unable to decode container path stat header: %s", err)
100
+	}
101
+
102
+	return stat, err
103
+}
0 104
new file mode 100644
... ...
@@ -0,0 +1,168 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"errors"
6
+	"net/url"
7
+	"path"
8
+	"sort"
9
+	"strings"
10
+
11
+	"github.com/moby/moby/api/types/container"
12
+	"github.com/moby/moby/api/types/network"
13
+	"github.com/moby/moby/api/types/versions"
14
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
15
+)
16
+
17
+// ContainerCreate creates a new container based on the given configuration.
18
+// It can be associated with a name, but it's not mandatory.
19
+func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
20
+	var response container.CreateResponse
21
+
22
+	// Make sure we negotiated (if the client is configured to do so),
23
+	// as code below contains API-version specific handling of options.
24
+	//
25
+	// Normally, version-negotiation (if enabled) would not happen until
26
+	// the API request is made.
27
+	if err := cli.checkVersion(ctx); err != nil {
28
+		return response, err
29
+	}
30
+
31
+	if err := cli.NewVersionError(ctx, "1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil {
32
+		return response, err
33
+	}
34
+	if err := cli.NewVersionError(ctx, "1.41", "specify container image platform"); platform != nil && err != nil {
35
+		return response, err
36
+	}
37
+	if err := cli.NewVersionError(ctx, "1.44", "specify health-check start interval"); config != nil && config.Healthcheck != nil && config.Healthcheck.StartInterval != 0 && err != nil {
38
+		return response, err
39
+	}
40
+	if err := cli.NewVersionError(ctx, "1.44", "specify mac-address per network"); hasEndpointSpecificMacAddress(networkingConfig) && err != nil {
41
+		return response, err
42
+	}
43
+
44
+	if hostConfig != nil {
45
+		if versions.LessThan(cli.ClientVersion(), "1.25") {
46
+			// When using API 1.24 and under, the client is responsible for removing the container
47
+			hostConfig.AutoRemove = false
48
+		}
49
+		if versions.GreaterThanOrEqualTo(cli.ClientVersion(), "1.42") || versions.LessThan(cli.ClientVersion(), "1.40") {
50
+			// KernelMemory was added in API 1.40, and deprecated in API 1.42
51
+			hostConfig.KernelMemory = 0
52
+		}
53
+		if platform != nil && platform.OS == "linux" && versions.LessThan(cli.ClientVersion(), "1.42") {
54
+			// When using API under 1.42, the Linux daemon doesn't respect the ConsoleSize
55
+			hostConfig.ConsoleSize = [2]uint{0, 0}
56
+		}
57
+		if versions.LessThan(cli.ClientVersion(), "1.44") {
58
+			for _, m := range hostConfig.Mounts {
59
+				if m.BindOptions != nil {
60
+					// ReadOnlyNonRecursive can be safely ignored when API < 1.44
61
+					if m.BindOptions.ReadOnlyForceRecursive {
62
+						return response, errors.New("bind-recursive=readonly requires API v1.44 or later")
63
+					}
64
+					if m.BindOptions.NonRecursive && versions.LessThan(cli.ClientVersion(), "1.40") {
65
+						return response, errors.New("bind-recursive=disabled requires API v1.40 or later")
66
+					}
67
+				}
68
+			}
69
+		}
70
+
71
+		hostConfig.CapAdd = normalizeCapabilities(hostConfig.CapAdd)
72
+		hostConfig.CapDrop = normalizeCapabilities(hostConfig.CapDrop)
73
+	}
74
+
75
+	// Since API 1.44, the container-wide MacAddress is deprecated and will trigger a WARNING if it's specified.
76
+	if versions.GreaterThanOrEqualTo(cli.ClientVersion(), "1.44") {
77
+		config.MacAddress = "" //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
78
+	}
79
+
80
+	query := url.Values{}
81
+	if p := formatPlatform(platform); p != "" {
82
+		query.Set("platform", p)
83
+	}
84
+
85
+	if containerName != "" {
86
+		query.Set("name", containerName)
87
+	}
88
+
89
+	body := container.CreateRequest{
90
+		Config:           config,
91
+		HostConfig:       hostConfig,
92
+		NetworkingConfig: networkingConfig,
93
+	}
94
+
95
+	resp, err := cli.post(ctx, "/containers/create", query, body, nil)
96
+	defer ensureReaderClosed(resp)
97
+	if err != nil {
98
+		return response, err
99
+	}
100
+
101
+	err = json.NewDecoder(resp.Body).Decode(&response)
102
+	return response, err
103
+}
104
+
105
+// formatPlatform returns a formatted string representing platform (e.g. linux/arm/v7).
106
+//
107
+// Similar to containerd's platforms.Format(), but does allow components to be
108
+// omitted (e.g. pass "architecture" only, without "os":
109
+// https://github.com/containerd/containerd/blob/v1.5.2/platforms/platforms.go#L243-L263
110
+func formatPlatform(platform *ocispec.Platform) string {
111
+	if platform == nil {
112
+		return ""
113
+	}
114
+	return path.Join(platform.OS, platform.Architecture, platform.Variant)
115
+}
116
+
117
+// hasEndpointSpecificMacAddress checks whether one of the endpoint in networkingConfig has a MacAddress defined.
118
+func hasEndpointSpecificMacAddress(networkingConfig *network.NetworkingConfig) bool {
119
+	if networkingConfig == nil {
120
+		return false
121
+	}
122
+	for _, endpoint := range networkingConfig.EndpointsConfig {
123
+		if endpoint.MacAddress != "" {
124
+			return true
125
+		}
126
+	}
127
+	return false
128
+}
129
+
130
+// allCapabilities is a magic value for "all capabilities"
131
+const allCapabilities = "ALL"
132
+
133
+// normalizeCapabilities normalizes capabilities to their canonical form,
134
+// removes duplicates, and sorts the results.
135
+//
136
+// It is similar to [github.com/docker/docker/oci/caps.NormalizeLegacyCapabilities],
137
+// but performs no validation based on supported capabilities.
138
+func normalizeCapabilities(caps []string) []string {
139
+	var normalized []string
140
+
141
+	unique := make(map[string]struct{})
142
+	for _, c := range caps {
143
+		c = normalizeCap(c)
144
+		if _, ok := unique[c]; ok {
145
+			continue
146
+		}
147
+		unique[c] = struct{}{}
148
+		normalized = append(normalized, c)
149
+	}
150
+
151
+	sort.Strings(normalized)
152
+	return normalized
153
+}
154
+
155
+// normalizeCap normalizes a capability to its canonical format by upper-casing
156
+// and adding a "CAP_" prefix (if not yet present). It also accepts the "ALL"
157
+// magic-value.
158
+func normalizeCap(capability string) string {
159
+	capability = strings.ToUpper(capability)
160
+	if capability == allCapabilities {
161
+		return capability
162
+	}
163
+	if !strings.HasPrefix(capability, "CAP_") {
164
+		capability = "CAP_" + capability
165
+	}
166
+	return capability
167
+}
0 168
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types/container"
8
+)
9
+
10
+// ContainerDiff shows differences in a container filesystem since it was started.
11
+func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]container.FilesystemChange, error) {
12
+	containerID, err := trimID("container", containerID)
13
+	if err != nil {
14
+		return nil, err
15
+	}
16
+
17
+	resp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil)
18
+	defer ensureReaderClosed(resp)
19
+	if err != nil {
20
+		return nil, err
21
+	}
22
+
23
+	var changes []container.FilesystemChange
24
+	err = json.NewDecoder(resp.Body).Decode(&changes)
25
+	if err != nil {
26
+		return nil, err
27
+	}
28
+	return changes, err
29
+}
0 30
new file mode 100644
... ...
@@ -0,0 +1,94 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/http"
6
+
7
+	"github.com/moby/moby/api/types"
8
+	"github.com/moby/moby/api/types/container"
9
+	"github.com/moby/moby/api/types/versions"
10
+)
11
+
12
+// ContainerExecCreate creates a new exec configuration to run an exec process.
13
+func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options container.ExecOptions) (container.ExecCreateResponse, error) {
14
+	containerID, err := trimID("container", containerID)
15
+	if err != nil {
16
+		return container.ExecCreateResponse{}, err
17
+	}
18
+
19
+	// Make sure we negotiated (if the client is configured to do so),
20
+	// as code below contains API-version specific handling of options.
21
+	//
22
+	// Normally, version-negotiation (if enabled) would not happen until
23
+	// the API request is made.
24
+	if err := cli.checkVersion(ctx); err != nil {
25
+		return container.ExecCreateResponse{}, err
26
+	}
27
+
28
+	if err := cli.NewVersionError(ctx, "1.25", "env"); len(options.Env) != 0 && err != nil {
29
+		return container.ExecCreateResponse{}, err
30
+	}
31
+	if versions.LessThan(cli.ClientVersion(), "1.42") {
32
+		options.ConsoleSize = nil
33
+	}
34
+
35
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, options, nil)
36
+	defer ensureReaderClosed(resp)
37
+	if err != nil {
38
+		return container.ExecCreateResponse{}, err
39
+	}
40
+
41
+	var response container.ExecCreateResponse
42
+	err = json.NewDecoder(resp.Body).Decode(&response)
43
+	return response, err
44
+}
45
+
46
+// ContainerExecStart starts an exec process already created in the docker host.
47
+func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config container.ExecStartOptions) error {
48
+	if versions.LessThan(cli.ClientVersion(), "1.42") {
49
+		config.ConsoleSize = nil
50
+	}
51
+	resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, config, nil)
52
+	ensureReaderClosed(resp)
53
+	return err
54
+}
55
+
56
+// ContainerExecAttach attaches a connection to an exec process in the server.
57
+//
58
+// It returns a [types.HijackedResponse] with the hijacked connection
59
+// and the a reader to get output. It's up to the called to close
60
+// the hijacked connection by calling [types.HijackedResponse.Close].
61
+//
62
+// The stream format on the response uses one of two formats:
63
+//
64
+//   - If the container is using a TTY, there is only a single stream (stdout), and
65
+//     data is copied directly from the container output stream, no extra
66
+//     multiplexing or headers.
67
+//   - If the container is *not* using a TTY, streams for stdout and stderr are
68
+//     multiplexed.
69
+//
70
+// You can use [github.com/docker/docker/pkg/stdcopy.StdCopy] to demultiplex this
71
+// stream. Refer to [Client.ContainerAttach] for details about the multiplexed
72
+// stream.
73
+func (cli *Client) ContainerExecAttach(ctx context.Context, execID string, config container.ExecAttachOptions) (types.HijackedResponse, error) {
74
+	if versions.LessThan(cli.ClientVersion(), "1.42") {
75
+		config.ConsoleSize = nil
76
+	}
77
+	return cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, config, http.Header{
78
+		"Content-Type": {"application/json"},
79
+	})
80
+}
81
+
82
+// ContainerExecInspect returns information about a specific exec process on the docker host.
83
+func (cli *Client) ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error) {
84
+	var response container.ExecInspect
85
+	resp, err := cli.get(ctx, "/exec/"+execID+"/json", nil, nil)
86
+	if err != nil {
87
+		return response, err
88
+	}
89
+
90
+	err = json.NewDecoder(resp.Body).Decode(&response)
91
+	ensureReaderClosed(resp)
92
+	return response, err
93
+}
0 94
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"net/url"
6
+)
7
+
8
+// ContainerExport retrieves the raw contents of a container
9
+// and returns them as an io.ReadCloser. It's up to the caller
10
+// to close the stream.
11
+func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) {
12
+	containerID, err := trimID("container", containerID)
13
+	if err != nil {
14
+		return nil, err
15
+	}
16
+
17
+	resp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil)
18
+	if err != nil {
19
+		return nil, err
20
+	}
21
+
22
+	return resp.Body, nil
23
+}
0 24
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"context"
5
+	"encoding/json"
6
+	"io"
7
+	"net/url"
8
+
9
+	"github.com/moby/moby/api/types/container"
10
+)
11
+
12
+// ContainerInspect returns the container information.
13
+func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (container.InspectResponse, error) {
14
+	containerID, err := trimID("container", containerID)
15
+	if err != nil {
16
+		return container.InspectResponse{}, err
17
+	}
18
+
19
+	resp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
20
+	defer ensureReaderClosed(resp)
21
+	if err != nil {
22
+		return container.InspectResponse{}, err
23
+	}
24
+
25
+	var response container.InspectResponse
26
+	err = json.NewDecoder(resp.Body).Decode(&response)
27
+	return response, err
28
+}
29
+
30
+// ContainerInspectWithRaw returns the container information and its raw representation.
31
+func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (container.InspectResponse, []byte, error) {
32
+	containerID, err := trimID("container", containerID)
33
+	if err != nil {
34
+		return container.InspectResponse{}, nil, err
35
+	}
36
+
37
+	query := url.Values{}
38
+	if getSize {
39
+		query.Set("size", "1")
40
+	}
41
+	resp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil)
42
+	defer ensureReaderClosed(resp)
43
+	if err != nil {
44
+		return container.InspectResponse{}, nil, err
45
+	}
46
+
47
+	body, err := io.ReadAll(resp.Body)
48
+	if err != nil {
49
+		return container.InspectResponse{}, nil, err
50
+	}
51
+
52
+	var response container.InspectResponse
53
+	rdr := bytes.NewReader(body)
54
+	err = json.NewDecoder(rdr).Decode(&response)
55
+	return response, body, err
56
+}
0 57
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+)
6
+
7
+// ContainerKill terminates the container process but does not remove the container from the docker host.
8
+func (cli *Client) ContainerKill(ctx context.Context, containerID, signal string) error {
9
+	containerID, err := trimID("container", containerID)
10
+	if err != nil {
11
+		return err
12
+	}
13
+
14
+	query := url.Values{}
15
+	if signal != "" {
16
+		query.Set("signal", signal)
17
+	}
18
+
19
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/kill", query, nil, nil)
20
+	ensureReaderClosed(resp)
21
+	return err
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+	"strconv"
7
+
8
+	"github.com/moby/moby/api/types/container"
9
+	"github.com/moby/moby/api/types/filters"
10
+)
11
+
12
+// ContainerList returns the list of containers in the docker host.
13
+func (cli *Client) ContainerList(ctx context.Context, options container.ListOptions) ([]container.Summary, error) {
14
+	query := url.Values{}
15
+
16
+	if options.All {
17
+		query.Set("all", "1")
18
+	}
19
+
20
+	if options.Limit > 0 {
21
+		query.Set("limit", strconv.Itoa(options.Limit))
22
+	}
23
+
24
+	if options.Since != "" {
25
+		query.Set("since", options.Since)
26
+	}
27
+
28
+	if options.Before != "" {
29
+		query.Set("before", options.Before)
30
+	}
31
+
32
+	if options.Size {
33
+		query.Set("size", "1")
34
+	}
35
+
36
+	if options.Filters.Len() > 0 {
37
+		//nolint:staticcheck // ignore SA1019 for old code
38
+		filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
39
+		if err != nil {
40
+			return nil, err
41
+		}
42
+
43
+		query.Set("filters", filterJSON)
44
+	}
45
+
46
+	resp, err := cli.get(ctx, "/containers/json", query, nil)
47
+	defer ensureReaderClosed(resp)
48
+	if err != nil {
49
+		return nil, err
50
+	}
51
+
52
+	var containers []container.Summary
53
+	err = json.NewDecoder(resp.Body).Decode(&containers)
54
+	return containers, err
55
+}
0 56
new file mode 100644
... ...
@@ -0,0 +1,85 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"net/url"
6
+	"time"
7
+
8
+	"github.com/moby/moby/api/types/container"
9
+	timetypes "github.com/moby/moby/api/types/time"
10
+	"github.com/pkg/errors"
11
+)
12
+
13
+// ContainerLogs returns the logs generated by a container in an io.ReadCloser.
14
+// It's up to the caller to close the stream.
15
+//
16
+// The stream format on the response will be in one of two formats:
17
+//
18
+// If the container is using a TTY, there is only a single stream (stdout), and
19
+// data is copied directly from the container output stream, no extra
20
+// multiplexing or headers.
21
+//
22
+// If the container is *not* using a TTY, streams for stdout and stderr are
23
+// multiplexed.
24
+// The format of the multiplexed stream is as follows:
25
+//
26
+//	[8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}[]byte{OUTPUT}
27
+//
28
+// STREAM_TYPE can be 1 for stdout and 2 for stderr
29
+//
30
+// SIZE1, SIZE2, SIZE3, and SIZE4 are four bytes of uint32 encoded as big endian.
31
+// This is the size of OUTPUT.
32
+//
33
+// You can use github.com/docker/docker/pkg/stdcopy.StdCopy to demultiplex this
34
+// stream.
35
+func (cli *Client) ContainerLogs(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
36
+	containerID, err := trimID("container", containerID)
37
+	if err != nil {
38
+		return nil, err
39
+	}
40
+
41
+	query := url.Values{}
42
+	if options.ShowStdout {
43
+		query.Set("stdout", "1")
44
+	}
45
+
46
+	if options.ShowStderr {
47
+		query.Set("stderr", "1")
48
+	}
49
+
50
+	if options.Since != "" {
51
+		ts, err := timetypes.GetTimestamp(options.Since, time.Now())
52
+		if err != nil {
53
+			return nil, errors.Wrap(err, `invalid value for "since"`)
54
+		}
55
+		query.Set("since", ts)
56
+	}
57
+
58
+	if options.Until != "" {
59
+		ts, err := timetypes.GetTimestamp(options.Until, time.Now())
60
+		if err != nil {
61
+			return nil, errors.Wrap(err, `invalid value for "until"`)
62
+		}
63
+		query.Set("until", ts)
64
+	}
65
+
66
+	if options.Timestamps {
67
+		query.Set("timestamps", "1")
68
+	}
69
+
70
+	if options.Details {
71
+		query.Set("details", "1")
72
+	}
73
+
74
+	if options.Follow {
75
+		query.Set("follow", "1")
76
+	}
77
+	query.Set("tail", options.Tail)
78
+
79
+	resp, err := cli.get(ctx, "/containers/"+containerID+"/logs", query, nil)
80
+	if err != nil {
81
+		return nil, err
82
+	}
83
+	return resp.Body, nil
84
+}
0 85
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+package client
1
+
2
+import "context"
3
+
4
+// ContainerPause pauses the main process of a given container without terminating it.
5
+func (cli *Client) ContainerPause(ctx context.Context, containerID string) error {
6
+	containerID, err := trimID("container", containerID)
7
+	if err != nil {
8
+		return err
9
+	}
10
+
11
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/pause", nil, nil, nil)
12
+	ensureReaderClosed(resp)
13
+	return err
14
+}
0 15
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"fmt"
6
+
7
+	"github.com/moby/moby/api/types/container"
8
+	"github.com/moby/moby/api/types/filters"
9
+)
10
+
11
+// ContainersPrune requests the daemon to delete unused data
12
+func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
13
+	if err := cli.NewVersionError(ctx, "1.25", "container prune"); err != nil {
14
+		return container.PruneReport{}, err
15
+	}
16
+
17
+	query, err := getFiltersQuery(pruneFilters)
18
+	if err != nil {
19
+		return container.PruneReport{}, err
20
+	}
21
+
22
+	resp, err := cli.post(ctx, "/containers/prune", query, nil, nil)
23
+	defer ensureReaderClosed(resp)
24
+	if err != nil {
25
+		return container.PruneReport{}, err
26
+	}
27
+
28
+	var report container.PruneReport
29
+	if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
30
+		return container.PruneReport{}, fmt.Errorf("Error retrieving disk usage: %v", err)
31
+	}
32
+
33
+	return report, nil
34
+}
0 35
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+
6
+	"github.com/moby/moby/api/types/container"
7
+)
8
+
9
+// ContainerRemove kills and removes a container from the docker host.
10
+func (cli *Client) ContainerRemove(ctx context.Context, containerID string, options container.RemoveOptions) error {
11
+	containerID, err := trimID("container", containerID)
12
+	if err != nil {
13
+		return err
14
+	}
15
+
16
+	query := url.Values{}
17
+	if options.RemoveVolumes {
18
+		query.Set("v", "1")
19
+	}
20
+	if options.RemoveLinks {
21
+		query.Set("link", "1")
22
+	}
23
+
24
+	if options.Force {
25
+		query.Set("force", "1")
26
+	}
27
+
28
+	resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil)
29
+	defer ensureReaderClosed(resp)
30
+	return err
31
+}
0 32
new file mode 100644
... ...
@@ -0,0 +1,20 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+)
6
+
7
+// ContainerRename changes the name of a given container.
8
+func (cli *Client) ContainerRename(ctx context.Context, containerID, newContainerName string) error {
9
+	containerID, err := trimID("container", containerID)
10
+	if err != nil {
11
+		return err
12
+	}
13
+
14
+	query := url.Values{}
15
+	query.Set("name", newContainerName)
16
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/rename", query, nil, nil)
17
+	ensureReaderClosed(resp)
18
+	return err
19
+}
0 20
new file mode 100644
... ...
@@ -0,0 +1,38 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+	"strconv"
6
+
7
+	"github.com/moby/moby/api/types/container"
8
+)
9
+
10
+// ContainerResize changes the size of the tty for a container.
11
+func (cli *Client) ContainerResize(ctx context.Context, containerID string, options container.ResizeOptions) error {
12
+	containerID, err := trimID("container", containerID)
13
+	if err != nil {
14
+		return err
15
+	}
16
+	return cli.resize(ctx, "/containers/"+containerID, options.Height, options.Width)
17
+}
18
+
19
+// ContainerExecResize changes the size of the tty for an exec process running inside a container.
20
+func (cli *Client) ContainerExecResize(ctx context.Context, execID string, options container.ResizeOptions) error {
21
+	execID, err := trimID("exec", execID)
22
+	if err != nil {
23
+		return err
24
+	}
25
+	return cli.resize(ctx, "/exec/"+execID, options.Height, options.Width)
26
+}
27
+
28
+func (cli *Client) resize(ctx context.Context, basePath string, height, width uint) error {
29
+	// FIXME(thaJeztah): the API / backend accepts uint32, but container.ResizeOptions uses uint.
30
+	query := url.Values{}
31
+	query.Set("h", strconv.FormatUint(uint64(height), 10))
32
+	query.Set("w", strconv.FormatUint(uint64(width), 10))
33
+
34
+	resp, err := cli.post(ctx, basePath+"/resize", query, nil, nil)
35
+	ensureReaderClosed(resp)
36
+	return err
37
+}
0 38
new file mode 100644
... ...
@@ -0,0 +1,41 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+	"strconv"
6
+
7
+	"github.com/moby/moby/api/types/container"
8
+	"github.com/moby/moby/api/types/versions"
9
+)
10
+
11
+// ContainerRestart stops and starts a container again.
12
+// It makes the daemon wait for the container to be up again for
13
+// a specific amount of time, given the timeout.
14
+func (cli *Client) ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error {
15
+	containerID, err := trimID("container", containerID)
16
+	if err != nil {
17
+		return err
18
+	}
19
+
20
+	query := url.Values{}
21
+	if options.Timeout != nil {
22
+		query.Set("t", strconv.Itoa(*options.Timeout))
23
+	}
24
+	if options.Signal != "" {
25
+		// Make sure we negotiated (if the client is configured to do so),
26
+		// as code below contains API-version specific handling of options.
27
+		//
28
+		// Normally, version-negotiation (if enabled) would not happen until
29
+		// the API request is made.
30
+		if err := cli.checkVersion(ctx); err != nil {
31
+			return err
32
+		}
33
+		if versions.GreaterThanOrEqualTo(cli.version, "1.42") {
34
+			query.Set("signal", options.Signal)
35
+		}
36
+	}
37
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/restart", query, nil, nil)
38
+	ensureReaderClosed(resp)
39
+	return err
40
+}
0 41
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+
6
+	"github.com/moby/moby/api/types/container"
7
+)
8
+
9
+// ContainerStart sends a request to the docker daemon to start a container.
10
+func (cli *Client) ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error {
11
+	containerID, err := trimID("container", containerID)
12
+	if err != nil {
13
+		return err
14
+	}
15
+
16
+	query := url.Values{}
17
+	if options.CheckpointID != "" {
18
+		query.Set("checkpoint", options.CheckpointID)
19
+	}
20
+	if options.CheckpointDir != "" {
21
+		query.Set("checkpoint-dir", options.CheckpointDir)
22
+	}
23
+
24
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/start", query, nil, nil)
25
+	ensureReaderClosed(resp)
26
+	return err
27
+}
0 28
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+
6
+	"github.com/moby/moby/api/types/container"
7
+)
8
+
9
+// ContainerStats returns near realtime stats for a given container.
10
+// It's up to the caller to close the io.ReadCloser returned.
11
+func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (container.StatsResponseReader, error) {
12
+	containerID, err := trimID("container", containerID)
13
+	if err != nil {
14
+		return container.StatsResponseReader{}, err
15
+	}
16
+
17
+	query := url.Values{}
18
+	query.Set("stream", "0")
19
+	if stream {
20
+		query.Set("stream", "1")
21
+	}
22
+
23
+	resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil)
24
+	if err != nil {
25
+		return container.StatsResponseReader{}, err
26
+	}
27
+
28
+	return container.StatsResponseReader{
29
+		Body:   resp.Body,
30
+		OSType: resp.Header.Get("Ostype"),
31
+	}, nil
32
+}
33
+
34
+// ContainerStatsOneShot gets a single stat entry from a container.
35
+// It differs from `ContainerStats` in that the API should not wait to prime the stats
36
+func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string) (container.StatsResponseReader, error) {
37
+	containerID, err := trimID("container", containerID)
38
+	if err != nil {
39
+		return container.StatsResponseReader{}, err
40
+	}
41
+
42
+	query := url.Values{}
43
+	query.Set("stream", "0")
44
+	query.Set("one-shot", "1")
45
+
46
+	resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil)
47
+	if err != nil {
48
+		return container.StatsResponseReader{}, err
49
+	}
50
+
51
+	return container.StatsResponseReader{
52
+		Body:   resp.Body,
53
+		OSType: resp.Header.Get("Ostype"),
54
+	}, nil
55
+}
0 56
new file mode 100644
... ...
@@ -0,0 +1,45 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+	"strconv"
6
+
7
+	"github.com/moby/moby/api/types/container"
8
+	"github.com/moby/moby/api/types/versions"
9
+)
10
+
11
+// ContainerStop stops a container. In case the container fails to stop
12
+// gracefully within a time frame specified by the timeout argument,
13
+// it is forcefully terminated (killed).
14
+//
15
+// If the timeout is nil, the container's StopTimeout value is used, if set,
16
+// otherwise the engine default. A negative timeout value can be specified,
17
+// meaning no timeout, i.e. no forceful termination is performed.
18
+func (cli *Client) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error {
19
+	containerID, err := trimID("container", containerID)
20
+	if err != nil {
21
+		return err
22
+	}
23
+
24
+	query := url.Values{}
25
+	if options.Timeout != nil {
26
+		query.Set("t", strconv.Itoa(*options.Timeout))
27
+	}
28
+	if options.Signal != "" {
29
+		// Make sure we negotiated (if the client is configured to do so),
30
+		// as code below contains API-version specific handling of options.
31
+		//
32
+		// Normally, version-negotiation (if enabled) would not happen until
33
+		// the API request is made.
34
+		if err := cli.checkVersion(ctx); err != nil {
35
+			return err
36
+		}
37
+		if versions.GreaterThanOrEqualTo(cli.version, "1.42") {
38
+			query.Set("signal", options.Signal)
39
+		}
40
+	}
41
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil)
42
+	ensureReaderClosed(resp)
43
+	return err
44
+}
0 45
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+	"strings"
7
+
8
+	"github.com/moby/moby/api/types/container"
9
+)
10
+
11
+// ContainerTop shows process information from within a container.
12
+func (cli *Client) ContainerTop(ctx context.Context, containerID string, arguments []string) (container.TopResponse, error) {
13
+	containerID, err := trimID("container", containerID)
14
+	if err != nil {
15
+		return container.TopResponse{}, err
16
+	}
17
+
18
+	query := url.Values{}
19
+	if len(arguments) > 0 {
20
+		query.Set("ps_args", strings.Join(arguments, " "))
21
+	}
22
+
23
+	resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil)
24
+	defer ensureReaderClosed(resp)
25
+	if err != nil {
26
+		return container.TopResponse{}, err
27
+	}
28
+
29
+	var response container.TopResponse
30
+	err = json.NewDecoder(resp.Body).Decode(&response)
31
+	return response, err
32
+}
0 33
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+package client
1
+
2
+import "context"
3
+
4
+// ContainerUnpause resumes the process execution within a container
5
+func (cli *Client) ContainerUnpause(ctx context.Context, containerID string) error {
6
+	containerID, err := trimID("container", containerID)
7
+	if err != nil {
8
+		return err
9
+	}
10
+
11
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/unpause", nil, nil, nil)
12
+	ensureReaderClosed(resp)
13
+	return err
14
+}
0 15
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+
6
+	"github.com/moby/moby/api/types/container"
7
+)
8
+
9
+// ContainerUpdate updates the resources of a container.
10
+func (cli *Client) ContainerUpdate(ctx context.Context, containerID string, updateConfig container.UpdateConfig) (container.UpdateResponse, error) {
11
+	containerID, err := trimID("container", containerID)
12
+	if err != nil {
13
+		return container.UpdateResponse{}, err
14
+	}
15
+
16
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil)
17
+	defer ensureReaderClosed(resp)
18
+	if err != nil {
19
+		return container.UpdateResponse{}, err
20
+	}
21
+
22
+	var response container.UpdateResponse
23
+	err = json.NewDecoder(resp.Body).Decode(&response)
24
+	return response, err
25
+}
0 26
new file mode 100644
... ...
@@ -0,0 +1,122 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"context"
5
+	"encoding/json"
6
+	"errors"
7
+	"io"
8
+	"net/url"
9
+
10
+	"github.com/moby/moby/api/types/container"
11
+	"github.com/moby/moby/api/types/versions"
12
+)
13
+
14
+const containerWaitErrorMsgLimit = 2 * 1024 /* Max: 2KiB */
15
+
16
+// ContainerWait waits until the specified container is in a certain state
17
+// indicated by the given condition, either "not-running" (default),
18
+// "next-exit", or "removed".
19
+//
20
+// If this client's API version is before 1.30, condition is ignored and
21
+// ContainerWait will return immediately with the two channels, as the server
22
+// will wait as if the condition were "not-running".
23
+//
24
+// If this client's API version is at least 1.30, ContainerWait blocks until
25
+// the request has been acknowledged by the server (with a response header),
26
+// then returns two channels on which the caller can wait for the exit status
27
+// of the container or an error if there was a problem either beginning the
28
+// wait request or in getting the response. This allows the caller to
29
+// synchronize ContainerWait with other calls, such as specifying a
30
+// "next-exit" condition before issuing a ContainerStart request.
31
+func (cli *Client) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
32
+	resultC := make(chan container.WaitResponse)
33
+	errC := make(chan error, 1)
34
+
35
+	containerID, err := trimID("container", containerID)
36
+	if err != nil {
37
+		errC <- err
38
+		return resultC, errC
39
+	}
40
+
41
+	// Make sure we negotiated (if the client is configured to do so),
42
+	// as code below contains API-version specific handling of options.
43
+	//
44
+	// Normally, version-negotiation (if enabled) would not happen until
45
+	// the API request is made.
46
+	if err := cli.checkVersion(ctx); err != nil {
47
+		errC <- err
48
+		return resultC, errC
49
+	}
50
+	if versions.LessThan(cli.ClientVersion(), "1.30") {
51
+		return cli.legacyContainerWait(ctx, containerID)
52
+	}
53
+
54
+	query := url.Values{}
55
+	if condition != "" {
56
+		query.Set("condition", string(condition))
57
+	}
58
+
59
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", query, nil, nil)
60
+	if err != nil {
61
+		defer ensureReaderClosed(resp)
62
+		errC <- err
63
+		return resultC, errC
64
+	}
65
+
66
+	go func() {
67
+		defer ensureReaderClosed(resp)
68
+
69
+		responseText := bytes.NewBuffer(nil)
70
+		stream := io.TeeReader(resp.Body, responseText)
71
+
72
+		var res container.WaitResponse
73
+		if err := json.NewDecoder(stream).Decode(&res); err != nil {
74
+			// NOTE(nicks): The /wait API does not work well with HTTP proxies.
75
+			// At any time, the proxy could cut off the response stream.
76
+			//
77
+			// But because the HTTP status has already been written, the proxy's
78
+			// only option is to write a plaintext error message.
79
+			//
80
+			// If there's a JSON parsing error, read the real error message
81
+			// off the body and send it to the client.
82
+			if errors.As(err, new(*json.SyntaxError)) {
83
+				_, _ = io.ReadAll(io.LimitReader(stream, containerWaitErrorMsgLimit))
84
+				errC <- errors.New(responseText.String())
85
+			} else {
86
+				errC <- err
87
+			}
88
+			return
89
+		}
90
+
91
+		resultC <- res
92
+	}()
93
+
94
+	return resultC, errC
95
+}
96
+
97
+// legacyContainerWait returns immediately and doesn't have an option to wait
98
+// until the container is removed.
99
+func (cli *Client) legacyContainerWait(ctx context.Context, containerID string) (<-chan container.WaitResponse, <-chan error) {
100
+	resultC := make(chan container.WaitResponse)
101
+	errC := make(chan error)
102
+
103
+	go func() {
104
+		resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil)
105
+		if err != nil {
106
+			errC <- err
107
+			return
108
+		}
109
+		defer ensureReaderClosed(resp)
110
+
111
+		var res container.WaitResponse
112
+		if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
113
+			errC <- err
114
+			return
115
+		}
116
+
117
+		resultC <- res
118
+	}()
119
+
120
+	return resultC, errC
121
+}
0 122
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"fmt"
6
+	"net/url"
7
+
8
+	"github.com/moby/moby/api/types"
9
+)
10
+
11
+// DiskUsage requests the current data usage from the daemon
12
+func (cli *Client) DiskUsage(ctx context.Context, options types.DiskUsageOptions) (types.DiskUsage, error) {
13
+	var query url.Values
14
+	if len(options.Types) > 0 {
15
+		query = url.Values{}
16
+		for _, t := range options.Types {
17
+			query.Add("type", string(t))
18
+		}
19
+	}
20
+
21
+	resp, err := cli.get(ctx, "/system/df", query, nil)
22
+	defer ensureReaderClosed(resp)
23
+	if err != nil {
24
+		return types.DiskUsage{}, err
25
+	}
26
+
27
+	var du types.DiskUsage
28
+	if err := json.NewDecoder(resp.Body).Decode(&du); err != nil {
29
+		return types.DiskUsage{}, fmt.Errorf("Error retrieving disk usage: %v", err)
30
+	}
31
+	return du, nil
32
+}
0 33
new file mode 100644
... ...
@@ -0,0 +1,39 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/http"
6
+	"net/url"
7
+
8
+	"github.com/moby/moby/api/types/registry"
9
+)
10
+
11
+// DistributionInspect returns the image digest with the full manifest.
12
+func (cli *Client) DistributionInspect(ctx context.Context, imageRef, encodedRegistryAuth string) (registry.DistributionInspect, error) {
13
+	if imageRef == "" {
14
+		return registry.DistributionInspect{}, objectNotFoundError{object: "distribution", id: imageRef}
15
+	}
16
+
17
+	if err := cli.NewVersionError(ctx, "1.30", "distribution inspect"); err != nil {
18
+		return registry.DistributionInspect{}, err
19
+	}
20
+
21
+	var headers http.Header
22
+	if encodedRegistryAuth != "" {
23
+		headers = http.Header{
24
+			registry.AuthHeader: {encodedRegistryAuth},
25
+		}
26
+	}
27
+
28
+	// Contact the registry to retrieve digest and platform information
29
+	resp, err := cli.get(ctx, "/distribution/"+imageRef+"/json", url.Values{}, headers)
30
+	defer ensureReaderClosed(resp)
31
+	if err != nil {
32
+		return registry.DistributionInspect{}, err
33
+	}
34
+
35
+	var distributionInspect registry.DistributionInspect
36
+	err = json.NewDecoder(resp.Body).Decode(&distributionInspect)
37
+	return distributionInspect, err
38
+}
0 39
new file mode 100644
... ...
@@ -0,0 +1,90 @@
0
+package client
1
+
2
+const (
3
+	// EnvOverrideHost is the name of the environment variable that can be used
4
+	// to override the default host to connect to (DefaultDockerHost).
5
+	//
6
+	// This env-var is read by FromEnv and WithHostFromEnv and when set to a
7
+	// non-empty value, takes precedence over the default host (which is platform
8
+	// specific), or any host already set.
9
+	EnvOverrideHost = "DOCKER_HOST"
10
+
11
+	// EnvOverrideAPIVersion is the name of the environment variable that can
12
+	// be used to override the API version to use. Value should be
13
+	// formatted as MAJOR.MINOR, for example, "1.19".
14
+	//
15
+	// This env-var is read by FromEnv and WithVersionFromEnv and when set to a
16
+	// non-empty value, takes precedence over API version negotiation.
17
+	//
18
+	// This environment variable should be used for debugging purposes only, as
19
+	// it can set the client to use an incompatible (or invalid) API version.
20
+	EnvOverrideAPIVersion = "DOCKER_API_VERSION"
21
+
22
+	// EnvOverrideCertPath is the name of the environment variable that can be
23
+	// used to specify the directory from which to load the TLS certificates
24
+	// (ca.pem, cert.pem, key.pem) from. These certificates are used to configure
25
+	// the Client for a TCP connection protected by TLS client authentication.
26
+	//
27
+	// TLS certificate verification is enabled by default if the Client is configured
28
+	// to use a TLS connection. Refer to EnvTLSVerify below to learn how to
29
+	// disable verification for testing purposes.
30
+	//
31
+	// WARNING: Access to the remote API is equivalent to root access to the
32
+	// host where the daemon runs. Do not expose the API without protection,
33
+	// and only if needed. Make sure you are familiar with the "daemon attack
34
+	// surface" (https://docs.docker.com/go/attack-surface/).
35
+	//
36
+	// For local access to the API, it is recommended to connect with the daemon
37
+	// using the default local socket connection (on Linux), or the named pipe
38
+	// (on Windows).
39
+	//
40
+	// If you need to access the API of a remote daemon, consider using an SSH
41
+	// (ssh://) connection, which is easier to set up, and requires no additional
42
+	// configuration if the host is accessible using ssh.
43
+	//
44
+	// If you cannot use the alternatives above, and you must expose the API over
45
+	// a TCP connection, refer to https://docs.docker.com/engine/security/protect-access/
46
+	// to learn how to configure the daemon and client to use a TCP connection
47
+	// with TLS client authentication. Make sure you know the differences between
48
+	// a regular TLS connection and a TLS connection protected by TLS client
49
+	// authentication, and verify that the API cannot be accessed by other clients.
50
+	EnvOverrideCertPath = "DOCKER_CERT_PATH"
51
+
52
+	// EnvTLSVerify is the name of the environment variable that can be used to
53
+	// enable or disable TLS certificate verification. When set to a non-empty
54
+	// value, TLS certificate verification is enabled, and the client is configured
55
+	// to use a TLS connection, using certificates from the default directories
56
+	// (within `~/.docker`); refer to EnvOverrideCertPath above for additional
57
+	// details.
58
+	//
59
+	// WARNING: Access to the remote API is equivalent to root access to the
60
+	// host where the daemon runs. Do not expose the API without protection,
61
+	// and only if needed. Make sure you are familiar with the "daemon attack
62
+	// surface" (https://docs.docker.com/go/attack-surface/).
63
+	//
64
+	// Before setting up your client and daemon to use a TCP connection with TLS
65
+	// client authentication, consider using one of the alternatives mentioned
66
+	// in EnvOverrideCertPath above.
67
+	//
68
+	// Disabling TLS certificate verification (for testing purposes)
69
+	//
70
+	// TLS certificate verification is enabled by default if the Client is configured
71
+	// to use a TLS connection, and it is highly recommended to keep verification
72
+	// enabled to prevent machine-in-the-middle attacks. Refer to the documentation
73
+	// at https://docs.docker.com/engine/security/protect-access/ and pages linked
74
+	// from that page to learn how to configure the daemon and client to use a
75
+	// TCP connection with TLS client authentication enabled.
76
+	//
77
+	// Set the "DOCKER_TLS_VERIFY" environment to an empty string ("") to
78
+	// disable TLS certificate verification. Disabling verification is insecure,
79
+	// so should only be done for testing purposes. From the Go documentation
80
+	// (https://pkg.go.dev/crypto/tls#Config):
81
+	//
82
+	// InsecureSkipVerify controls whether a client verifies the server's
83
+	// certificate chain and host name. If InsecureSkipVerify is true, crypto/tls
84
+	// accepts any certificate presented by the server and any host name in that
85
+	// certificate. In this mode, TLS is susceptible to machine-in-the-middle
86
+	// attacks unless custom verification is used. This should be used only for
87
+	// testing or in combination with VerifyConnection or VerifyPeerCertificate.
88
+	EnvTLSVerify = "DOCKER_TLS_VERIFY"
89
+)
0 90
new file mode 100644
... ...
@@ -0,0 +1,129 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"errors"
5
+	"fmt"
6
+	"net/http"
7
+
8
+	cerrdefs "github.com/containerd/errdefs"
9
+	"github.com/containerd/errdefs/pkg/errhttp"
10
+	"github.com/moby/moby/api/types/versions"
11
+)
12
+
13
+// errConnectionFailed implements an error returned when connection failed.
14
+type errConnectionFailed struct {
15
+	error
16
+}
17
+
18
+// Error returns a string representation of an errConnectionFailed
19
+func (e errConnectionFailed) Error() string {
20
+	return e.error.Error()
21
+}
22
+
23
+func (e errConnectionFailed) Unwrap() error {
24
+	return e.error
25
+}
26
+
27
+// IsErrConnectionFailed returns true if the error is caused by connection failed.
28
+func IsErrConnectionFailed(err error) bool {
29
+	return errors.As(err, &errConnectionFailed{})
30
+}
31
+
32
+// ErrorConnectionFailed returns an error with host in the error message when connection to docker daemon failed.
33
+//
34
+// Deprecated: this function was only used internally, and will be removed in the next release.
35
+func ErrorConnectionFailed(host string) error {
36
+	return connectionFailed(host)
37
+}
38
+
39
+// connectionFailed returns an error with host in the error message when connection
40
+// to docker daemon failed.
41
+func connectionFailed(host string) error {
42
+	var err error
43
+	if host == "" {
44
+		err = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?")
45
+	} else {
46
+		err = fmt.Errorf("Cannot connect to the Docker daemon at %s. Is the docker daemon running?", host)
47
+	}
48
+	return errConnectionFailed{error: err}
49
+}
50
+
51
+// IsErrNotFound returns true if the error is a NotFound error, which is returned
52
+// by the API when some object is not found. It is an alias for [cerrdefs.IsNotFound].
53
+//
54
+// Deprecated: use [cerrdefs.IsNotFound] instead.
55
+func IsErrNotFound(err error) bool {
56
+	return cerrdefs.IsNotFound(err)
57
+}
58
+
59
+type objectNotFoundError struct {
60
+	object string
61
+	id     string
62
+}
63
+
64
+func (e objectNotFoundError) NotFound() {}
65
+
66
+func (e objectNotFoundError) Error() string {
67
+	return fmt.Sprintf("Error: No such %s: %s", e.object, e.id)
68
+}
69
+
70
+// NewVersionError returns an error if the APIVersion required is less than the
71
+// current supported version.
72
+//
73
+// It performs API-version negotiation if the Client is configured with this
74
+// option, otherwise it assumes the latest API version is used.
75
+func (cli *Client) NewVersionError(ctx context.Context, APIrequired, feature string) error {
76
+	// Make sure we negotiated (if the client is configured to do so),
77
+	// as code below contains API-version specific handling of options.
78
+	//
79
+	// Normally, version-negotiation (if enabled) would not happen until
80
+	// the API request is made.
81
+	if err := cli.checkVersion(ctx); err != nil {
82
+		return err
83
+	}
84
+	if cli.version != "" && versions.LessThan(cli.version, APIrequired) {
85
+		return fmt.Errorf("%q requires API version %s, but the Docker daemon API version is %s", feature, APIrequired, cli.version)
86
+	}
87
+	return nil
88
+}
89
+
90
+type httpError struct {
91
+	err    error
92
+	errdef error
93
+}
94
+
95
+func (e *httpError) Error() string {
96
+	return e.err.Error()
97
+}
98
+
99
+func (e *httpError) Unwrap() error {
100
+	return e.err
101
+}
102
+
103
+func (e *httpError) Is(target error) bool {
104
+	return errors.Is(e.errdef, target)
105
+}
106
+
107
+// httpErrorFromStatusCode creates an errdef error, based on the provided HTTP status-code
108
+func httpErrorFromStatusCode(err error, statusCode int) error {
109
+	if err == nil {
110
+		return nil
111
+	}
112
+	base := errhttp.ToNative(statusCode)
113
+	if base != nil {
114
+		return &httpError{err: err, errdef: base}
115
+	}
116
+
117
+	switch {
118
+	case statusCode >= http.StatusOK && statusCode < http.StatusBadRequest:
119
+		// it's a client error
120
+		return err
121
+	case statusCode >= http.StatusBadRequest && statusCode < http.StatusInternalServerError:
122
+		return &httpError{err: err, errdef: cerrdefs.ErrInvalidArgument}
123
+	case statusCode >= http.StatusInternalServerError && statusCode < 600:
124
+		return &httpError{err: err, errdef: cerrdefs.ErrInternal}
125
+	default:
126
+		return &httpError{err: err, errdef: cerrdefs.ErrUnknown}
127
+	}
128
+}
0 129
new file mode 100644
... ...
@@ -0,0 +1,100 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+	"time"
7
+
8
+	"github.com/moby/moby/api/types/events"
9
+	"github.com/moby/moby/api/types/filters"
10
+	timetypes "github.com/moby/moby/api/types/time"
11
+)
12
+
13
+// Events returns a stream of events in the daemon. It's up to the caller to close the stream
14
+// by cancelling the context. Once the stream has been completely read an io.EOF error will
15
+// be sent over the error channel. If an error is sent all processing will be stopped. It's up
16
+// to the caller to reopen the stream in the event of an error by reinvoking this method.
17
+func (cli *Client) Events(ctx context.Context, options events.ListOptions) (<-chan events.Message, <-chan error) {
18
+	messages := make(chan events.Message)
19
+	errs := make(chan error, 1)
20
+
21
+	started := make(chan struct{})
22
+	go func() {
23
+		defer close(errs)
24
+
25
+		query, err := buildEventsQueryParams(cli.version, options)
26
+		if err != nil {
27
+			close(started)
28
+			errs <- err
29
+			return
30
+		}
31
+
32
+		resp, err := cli.get(ctx, "/events", query, nil)
33
+		if err != nil {
34
+			close(started)
35
+			errs <- err
36
+			return
37
+		}
38
+		defer resp.Body.Close()
39
+
40
+		decoder := json.NewDecoder(resp.Body)
41
+
42
+		close(started)
43
+		for {
44
+			select {
45
+			case <-ctx.Done():
46
+				errs <- ctx.Err()
47
+				return
48
+			default:
49
+				var event events.Message
50
+				if err := decoder.Decode(&event); err != nil {
51
+					errs <- err
52
+					return
53
+				}
54
+
55
+				select {
56
+				case messages <- event:
57
+				case <-ctx.Done():
58
+					errs <- ctx.Err()
59
+					return
60
+				}
61
+			}
62
+		}
63
+	}()
64
+	<-started
65
+
66
+	return messages, errs
67
+}
68
+
69
+func buildEventsQueryParams(cliVersion string, options events.ListOptions) (url.Values, error) {
70
+	query := url.Values{}
71
+	ref := time.Now()
72
+
73
+	if options.Since != "" {
74
+		ts, err := timetypes.GetTimestamp(options.Since, ref)
75
+		if err != nil {
76
+			return nil, err
77
+		}
78
+		query.Set("since", ts)
79
+	}
80
+
81
+	if options.Until != "" {
82
+		ts, err := timetypes.GetTimestamp(options.Until, ref)
83
+		if err != nil {
84
+			return nil, err
85
+		}
86
+		query.Set("until", ts)
87
+	}
88
+
89
+	if options.Filters.Len() > 0 {
90
+		//nolint:staticcheck // ignore SA1019 for old code
91
+		filterJSON, err := filters.ToParamWithVersion(cliVersion, options.Filters)
92
+		if err != nil {
93
+			return nil, err
94
+		}
95
+		query.Set("filters", filterJSON)
96
+	}
97
+
98
+	return query, nil
99
+}
0 100
new file mode 100644
... ...
@@ -0,0 +1,139 @@
0
+package client
1
+
2
+import (
3
+	"bufio"
4
+	"context"
5
+	"fmt"
6
+	"net"
7
+	"net/http"
8
+	"net/url"
9
+	"time"
10
+
11
+	"github.com/moby/moby/api/types"
12
+	"github.com/moby/moby/api/types/versions"
13
+	"github.com/pkg/errors"
14
+	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
15
+)
16
+
17
+// postHijacked sends a POST request and hijacks the connection.
18
+func (cli *Client) postHijacked(ctx context.Context, path string, query url.Values, body interface{}, headers map[string][]string) (types.HijackedResponse, error) {
19
+	bodyEncoded, err := encodeData(body)
20
+	if err != nil {
21
+		return types.HijackedResponse{}, err
22
+	}
23
+	req, err := cli.buildRequest(ctx, http.MethodPost, cli.getAPIPath(ctx, path, query), bodyEncoded, headers)
24
+	if err != nil {
25
+		return types.HijackedResponse{}, err
26
+	}
27
+	conn, mediaType, err := setupHijackConn(cli.dialer(), req, "tcp")
28
+	if err != nil {
29
+		return types.HijackedResponse{}, err
30
+	}
31
+
32
+	if versions.LessThan(cli.ClientVersion(), "1.42") {
33
+		// Prior to 1.42, Content-Type is always set to raw-stream and not relevant
34
+		mediaType = ""
35
+	}
36
+
37
+	return types.NewHijackedResponse(conn, mediaType), nil
38
+}
39
+
40
+// DialHijack returns a hijacked connection with negotiated protocol proto.
41
+func (cli *Client) DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error) {
42
+	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, http.NoBody)
43
+	if err != nil {
44
+		return nil, err
45
+	}
46
+	req = cli.addHeaders(req, meta)
47
+
48
+	conn, _, err := setupHijackConn(cli.Dialer(), req, proto)
49
+	return conn, err
50
+}
51
+
52
+func setupHijackConn(dialer func(context.Context) (net.Conn, error), req *http.Request, proto string) (_ net.Conn, _ string, retErr error) {
53
+	ctx := req.Context()
54
+	req.Header.Set("Connection", "Upgrade")
55
+	req.Header.Set("Upgrade", proto)
56
+
57
+	conn, err := dialer(ctx)
58
+	if err != nil {
59
+		return nil, "", errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
60
+	}
61
+	defer func() {
62
+		if retErr != nil {
63
+			conn.Close()
64
+		}
65
+	}()
66
+
67
+	// When we set up a TCP connection for hijack, there could be long periods
68
+	// of inactivity (a long running command with no output) that in certain
69
+	// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
70
+	// state. Setting TCP KeepAlive on the socket connection will prohibit
71
+	// ECONNTIMEOUT unless the socket connection truly is broken
72
+	if tcpConn, ok := conn.(*net.TCPConn); ok {
73
+		_ = tcpConn.SetKeepAlive(true)
74
+		_ = tcpConn.SetKeepAlivePeriod(30 * time.Second)
75
+	}
76
+
77
+	hc := &hijackedConn{conn, bufio.NewReader(conn)}
78
+
79
+	// Server hijacks the connection, error 'connection closed' expected
80
+	resp, err := otelhttp.NewTransport(hc).RoundTrip(req)
81
+	if err != nil {
82
+		return nil, "", err
83
+	}
84
+	if resp.StatusCode != http.StatusSwitchingProtocols {
85
+		_ = resp.Body.Close()
86
+		return nil, "", fmt.Errorf("unable to upgrade to %s, received %d", proto, resp.StatusCode)
87
+	}
88
+
89
+	if hc.r.Buffered() > 0 {
90
+		// If there is buffered content, wrap the connection.  We return an
91
+		// object that implements CloseWrite if the underlying connection
92
+		// implements it.
93
+		if _, ok := hc.Conn.(types.CloseWriter); ok {
94
+			conn = &hijackedConnCloseWriter{hc}
95
+		} else {
96
+			conn = hc
97
+		}
98
+	} else {
99
+		hc.r.Reset(nil)
100
+	}
101
+
102
+	return conn, resp.Header.Get("Content-Type"), nil
103
+}
104
+
105
+// hijackedConn wraps a net.Conn and is returned by setupHijackConn in the case
106
+// that a) there was already buffered data in the http layer when Hijack() was
107
+// called, and b) the underlying net.Conn does *not* implement CloseWrite().
108
+// hijackedConn does not implement CloseWrite() either.
109
+type hijackedConn struct {
110
+	net.Conn
111
+	r *bufio.Reader
112
+}
113
+
114
+func (c *hijackedConn) RoundTrip(req *http.Request) (*http.Response, error) {
115
+	if err := req.Write(c.Conn); err != nil {
116
+		return nil, err
117
+	}
118
+	return http.ReadResponse(c.r, req)
119
+}
120
+
121
+func (c *hijackedConn) Read(b []byte) (int, error) {
122
+	return c.r.Read(b)
123
+}
124
+
125
+// hijackedConnCloseWriter is a hijackedConn which additionally implements
126
+// CloseWrite().  It is returned by setupHijackConn in the case that a) there
127
+// was already buffered data in the http layer when Hijack() was called, and b)
128
+// the underlying net.Conn *does* implement CloseWrite().
129
+type hijackedConnCloseWriter struct {
130
+	*hijackedConn
131
+}
132
+
133
+var _ types.CloseWriter = &hijackedConnCloseWriter{}
134
+
135
+func (c *hijackedConnCloseWriter) CloseWrite() error {
136
+	conn := c.Conn.(types.CloseWriter)
137
+	return conn.CloseWrite()
138
+}
0 139
new file mode 100644
... ...
@@ -0,0 +1,182 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/base64"
5
+	"encoding/json"
6
+	"io"
7
+	"net/http"
8
+	"net/url"
9
+	"strconv"
10
+	"strings"
11
+
12
+	"github.com/moby/moby/api/types/build"
13
+	"github.com/moby/moby/api/types/container"
14
+	"github.com/moby/moby/api/types/network"
15
+)
16
+
17
+// ImageBuild sends a request to the daemon to build images.
18
+// The Body in the response implements an io.ReadCloser and it's up to the caller to
19
+// close it.
20
+func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options build.ImageBuildOptions) (build.ImageBuildResponse, error) {
21
+	query, err := cli.imageBuildOptionsToQuery(ctx, options)
22
+	if err != nil {
23
+		return build.ImageBuildResponse{}, err
24
+	}
25
+
26
+	buf, err := json.Marshal(options.AuthConfigs)
27
+	if err != nil {
28
+		return build.ImageBuildResponse{}, err
29
+	}
30
+
31
+	headers := http.Header{}
32
+	headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
33
+	headers.Set("Content-Type", "application/x-tar")
34
+
35
+	resp, err := cli.postRaw(ctx, "/build", query, buildContext, headers)
36
+	if err != nil {
37
+		return build.ImageBuildResponse{}, err
38
+	}
39
+
40
+	return build.ImageBuildResponse{
41
+		Body:   resp.Body,
42
+		OSType: resp.Header.Get("Ostype"),
43
+	}, nil
44
+}
45
+
46
+func (cli *Client) imageBuildOptionsToQuery(ctx context.Context, options build.ImageBuildOptions) (url.Values, error) {
47
+	query := url.Values{}
48
+	if len(options.Tags) > 0 {
49
+		query["t"] = options.Tags
50
+	}
51
+	if len(options.SecurityOpt) > 0 {
52
+		query["securityopt"] = options.SecurityOpt
53
+	}
54
+	if len(options.ExtraHosts) > 0 {
55
+		query["extrahosts"] = options.ExtraHosts
56
+	}
57
+	if options.SuppressOutput {
58
+		query.Set("q", "1")
59
+	}
60
+	if options.RemoteContext != "" {
61
+		query.Set("remote", options.RemoteContext)
62
+	}
63
+	if options.NoCache {
64
+		query.Set("nocache", "1")
65
+	}
66
+	if !options.Remove {
67
+		// only send value when opting out because the daemon's default is
68
+		// to remove intermediate containers after a successful build,
69
+		//
70
+		// TODO(thaJeztah): deprecate "Remove" option, and provide a "NoRemove" or "Keep" option instead.
71
+		query.Set("rm", "0")
72
+	}
73
+
74
+	if options.ForceRemove {
75
+		query.Set("forcerm", "1")
76
+	}
77
+
78
+	if options.PullParent {
79
+		query.Set("pull", "1")
80
+	}
81
+
82
+	if options.Squash {
83
+		if err := cli.NewVersionError(ctx, "1.25", "squash"); err != nil {
84
+			return query, err
85
+		}
86
+		query.Set("squash", "1")
87
+	}
88
+
89
+	if !container.Isolation.IsDefault(options.Isolation) {
90
+		query.Set("isolation", string(options.Isolation))
91
+	}
92
+
93
+	if options.CPUSetCPUs != "" {
94
+		query.Set("cpusetcpus", options.CPUSetCPUs)
95
+	}
96
+	if options.NetworkMode != "" && options.NetworkMode != network.NetworkDefault {
97
+		query.Set("networkmode", options.NetworkMode)
98
+	}
99
+	if options.CPUSetMems != "" {
100
+		query.Set("cpusetmems", options.CPUSetMems)
101
+	}
102
+	if options.CPUShares != 0 {
103
+		query.Set("cpushares", strconv.FormatInt(options.CPUShares, 10))
104
+	}
105
+	if options.CPUQuota != 0 {
106
+		query.Set("cpuquota", strconv.FormatInt(options.CPUQuota, 10))
107
+	}
108
+	if options.CPUPeriod != 0 {
109
+		query.Set("cpuperiod", strconv.FormatInt(options.CPUPeriod, 10))
110
+	}
111
+	if options.Memory != 0 {
112
+		query.Set("memory", strconv.FormatInt(options.Memory, 10))
113
+	}
114
+	if options.MemorySwap != 0 {
115
+		query.Set("memswap", strconv.FormatInt(options.MemorySwap, 10))
116
+	}
117
+	if options.CgroupParent != "" {
118
+		query.Set("cgroupparent", options.CgroupParent)
119
+	}
120
+	if options.ShmSize != 0 {
121
+		query.Set("shmsize", strconv.FormatInt(options.ShmSize, 10))
122
+	}
123
+	if options.Dockerfile != "" {
124
+		query.Set("dockerfile", options.Dockerfile)
125
+	}
126
+	if options.Target != "" {
127
+		query.Set("target", options.Target)
128
+	}
129
+	if len(options.Ulimits) != 0 {
130
+		ulimitsJSON, err := json.Marshal(options.Ulimits)
131
+		if err != nil {
132
+			return query, err
133
+		}
134
+		query.Set("ulimits", string(ulimitsJSON))
135
+	}
136
+	if len(options.BuildArgs) != 0 {
137
+		buildArgsJSON, err := json.Marshal(options.BuildArgs)
138
+		if err != nil {
139
+			return query, err
140
+		}
141
+		query.Set("buildargs", string(buildArgsJSON))
142
+	}
143
+	if len(options.Labels) != 0 {
144
+		labelsJSON, err := json.Marshal(options.Labels)
145
+		if err != nil {
146
+			return query, err
147
+		}
148
+		query.Set("labels", string(labelsJSON))
149
+	}
150
+	if len(options.CacheFrom) != 0 {
151
+		cacheFromJSON, err := json.Marshal(options.CacheFrom)
152
+		if err != nil {
153
+			return query, err
154
+		}
155
+		query.Set("cachefrom", string(cacheFromJSON))
156
+	}
157
+	if options.SessionID != "" {
158
+		query.Set("session", options.SessionID)
159
+	}
160
+	if options.Platform != "" {
161
+		if err := cli.NewVersionError(ctx, "1.32", "platform"); err != nil {
162
+			return query, err
163
+		}
164
+		query.Set("platform", strings.ToLower(options.Platform))
165
+	}
166
+	if options.BuildID != "" {
167
+		query.Set("buildid", options.BuildID)
168
+	}
169
+	if options.Version != "" {
170
+		query.Set("version", string(options.Version))
171
+	}
172
+
173
+	if options.Outputs != nil {
174
+		outputsJSON, err := json.Marshal(options.Outputs)
175
+		if err != nil {
176
+			return query, err
177
+		}
178
+		query.Set("outputs", string(outputsJSON))
179
+	}
180
+	return query, nil
181
+}
0 182
new file mode 100644
... ...
@@ -0,0 +1,48 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"net/http"
6
+	"net/url"
7
+	"strings"
8
+
9
+	"github.com/distribution/reference"
10
+	"github.com/moby/moby/api/types/image"
11
+	"github.com/moby/moby/api/types/registry"
12
+)
13
+
14
+// ImageCreate creates a new image based on the parent options.
15
+// It returns the JSON content in the response body.
16
+func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) {
17
+	ref, err := reference.ParseNormalizedNamed(parentReference)
18
+	if err != nil {
19
+		return nil, err
20
+	}
21
+
22
+	query := url.Values{}
23
+	query.Set("fromImage", ref.Name())
24
+	query.Set("tag", getAPITagFromNamedRef(ref))
25
+	if options.Platform != "" {
26
+		query.Set("platform", strings.ToLower(options.Platform))
27
+	}
28
+	resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth))
29
+	if err != nil {
30
+		return nil, err
31
+	}
32
+	return resp.Body, nil
33
+}
34
+
35
+func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) {
36
+	hdr := http.Header{}
37
+	if resolveAuth != nil {
38
+		registryAuth, err := resolveAuth(ctx)
39
+		if err != nil {
40
+			return nil, err
41
+		}
42
+		if registryAuth != "" {
43
+			hdr.Set(registry.AuthHeader, registryAuth)
44
+		}
45
+	}
46
+	return cli.post(ctx, "/images/create", query, nil, hdr)
47
+}
0 48
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"fmt"
6
+	"net/url"
7
+
8
+	"github.com/moby/moby/api/types/image"
9
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
10
+)
11
+
12
+// ImageHistoryWithPlatform sets the platform for the image history operation.
13
+func ImageHistoryWithPlatform(platform ocispec.Platform) ImageHistoryOption {
14
+	return imageHistoryOptionFunc(func(opt *imageHistoryOpts) error {
15
+		if opt.apiOptions.Platform != nil {
16
+			return fmt.Errorf("platform already set to %s", *opt.apiOptions.Platform)
17
+		}
18
+		opt.apiOptions.Platform = &platform
19
+		return nil
20
+	})
21
+}
22
+
23
+// ImageHistory returns the changes in an image in history format.
24
+func (cli *Client) ImageHistory(ctx context.Context, imageID string, historyOpts ...ImageHistoryOption) ([]image.HistoryResponseItem, error) {
25
+	query := url.Values{}
26
+
27
+	var opts imageHistoryOpts
28
+	for _, o := range historyOpts {
29
+		if err := o.Apply(&opts); err != nil {
30
+			return nil, err
31
+		}
32
+	}
33
+
34
+	if opts.apiOptions.Platform != nil {
35
+		if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
36
+			return nil, err
37
+		}
38
+
39
+		p, err := encodePlatform(opts.apiOptions.Platform)
40
+		if err != nil {
41
+			return nil, err
42
+		}
43
+		query.Set("platform", p)
44
+	}
45
+
46
+	resp, err := cli.get(ctx, "/images/"+imageID+"/history", query, nil)
47
+	defer ensureReaderClosed(resp)
48
+	if err != nil {
49
+		return nil, err
50
+	}
51
+
52
+	var history []image.HistoryResponseItem
53
+	err = json.NewDecoder(resp.Body).Decode(&history)
54
+	return history, err
55
+}
0 56
new file mode 100644
... ...
@@ -0,0 +1,19 @@
0
+package client
1
+
2
+import (
3
+	"github.com/moby/moby/api/types/image"
4
+)
5
+
6
+// ImageHistoryOption is a type representing functional options for the image history operation.
7
+type ImageHistoryOption interface {
8
+	Apply(*imageHistoryOpts) error
9
+}
10
+type imageHistoryOptionFunc func(opt *imageHistoryOpts) error
11
+
12
+func (f imageHistoryOptionFunc) Apply(o *imageHistoryOpts) error {
13
+	return f(o)
14
+}
15
+
16
+type imageHistoryOpts struct {
17
+	apiOptions image.HistoryOptions
18
+}
0 19
new file mode 100644
... ...
@@ -0,0 +1,48 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"net/url"
6
+	"strings"
7
+
8
+	"github.com/distribution/reference"
9
+	"github.com/moby/moby/api/types/image"
10
+)
11
+
12
+// ImageImport creates a new image based on the source options.
13
+// It returns the JSON content in the response body.
14
+func (cli *Client) ImageImport(ctx context.Context, source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) {
15
+	if ref != "" {
16
+		// Check if the given image name can be resolved
17
+		if _, err := reference.ParseNormalizedNamed(ref); err != nil {
18
+			return nil, err
19
+		}
20
+	}
21
+
22
+	query := url.Values{}
23
+	if source.SourceName != "" {
24
+		query.Set("fromSrc", source.SourceName)
25
+	}
26
+	if ref != "" {
27
+		query.Set("repo", ref)
28
+	}
29
+	if options.Tag != "" {
30
+		query.Set("tag", options.Tag)
31
+	}
32
+	if options.Message != "" {
33
+		query.Set("message", options.Message)
34
+	}
35
+	if options.Platform != "" {
36
+		query.Set("platform", strings.ToLower(options.Platform))
37
+	}
38
+	for _, change := range options.Changes {
39
+		query.Add("changes", change)
40
+	}
41
+
42
+	resp, err := cli.postRaw(ctx, "/images/create", query, source.Source, nil)
43
+	if err != nil {
44
+		return nil, err
45
+	}
46
+	return resp.Body, nil
47
+}
0 48
new file mode 100644
... ...
@@ -0,0 +1,76 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"context"
5
+	"encoding/json"
6
+	"fmt"
7
+	"io"
8
+	"net/url"
9
+
10
+	"github.com/moby/moby/api/types/image"
11
+)
12
+
13
+// ImageInspect returns the image information.
14
+func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts ...ImageInspectOption) (image.InspectResponse, error) {
15
+	if imageID == "" {
16
+		return image.InspectResponse{}, objectNotFoundError{object: "image", id: imageID}
17
+	}
18
+
19
+	var opts imageInspectOpts
20
+	for _, opt := range inspectOpts {
21
+		if err := opt.Apply(&opts); err != nil {
22
+			return image.InspectResponse{}, fmt.Errorf("error applying image inspect option: %w", err)
23
+		}
24
+	}
25
+
26
+	query := url.Values{}
27
+	if opts.apiOptions.Manifests {
28
+		if err := cli.NewVersionError(ctx, "1.48", "manifests"); err != nil {
29
+			return image.InspectResponse{}, err
30
+		}
31
+		query.Set("manifests", "1")
32
+	}
33
+
34
+	if opts.apiOptions.Platform != nil {
35
+		if err := cli.NewVersionError(ctx, "1.49", "platform"); err != nil {
36
+			return image.InspectResponse{}, err
37
+		}
38
+		platform, err := encodePlatform(opts.apiOptions.Platform)
39
+		if err != nil {
40
+			return image.InspectResponse{}, err
41
+		}
42
+		query.Set("platform", platform)
43
+	}
44
+
45
+	resp, err := cli.get(ctx, "/images/"+imageID+"/json", query, nil)
46
+	defer ensureReaderClosed(resp)
47
+	if err != nil {
48
+		return image.InspectResponse{}, err
49
+	}
50
+
51
+	buf := opts.raw
52
+	if buf == nil {
53
+		buf = &bytes.Buffer{}
54
+	}
55
+
56
+	if _, err := io.Copy(buf, resp.Body); err != nil {
57
+		return image.InspectResponse{}, err
58
+	}
59
+
60
+	var response image.InspectResponse
61
+	err = json.Unmarshal(buf.Bytes(), &response)
62
+	return response, err
63
+}
64
+
65
+// ImageInspectWithRaw returns the image information and its raw representation.
66
+//
67
+// Deprecated: Use [Client.ImageInspect] instead. Raw response can be obtained using the [ImageInspectWithRawResponse] option.
68
+func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (image.InspectResponse, []byte, error) {
69
+	var buf bytes.Buffer
70
+	resp, err := cli.ImageInspect(ctx, imageID, ImageInspectWithRawResponse(&buf))
71
+	if err != nil {
72
+		return image.InspectResponse{}, nil, err
73
+	}
74
+	return resp, buf.Bytes(), err
75
+}
0 76
new file mode 100644
... ...
@@ -0,0 +1,62 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+
5
+	"github.com/moby/moby/api/types/image"
6
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
7
+)
8
+
9
+// ImageInspectOption is a type representing functional options for the image inspect operation.
10
+type ImageInspectOption interface {
11
+	Apply(*imageInspectOpts) error
12
+}
13
+type imageInspectOptionFunc func(opt *imageInspectOpts) error
14
+
15
+func (f imageInspectOptionFunc) Apply(o *imageInspectOpts) error {
16
+	return f(o)
17
+}
18
+
19
+// ImageInspectWithRawResponse instructs the client to additionally store the
20
+// raw inspect response in the provided buffer.
21
+func ImageInspectWithRawResponse(raw *bytes.Buffer) ImageInspectOption {
22
+	return imageInspectOptionFunc(func(opts *imageInspectOpts) error {
23
+		opts.raw = raw
24
+		return nil
25
+	})
26
+}
27
+
28
+// ImageInspectWithManifests sets manifests API option for the image inspect operation.
29
+// This option is only available for API version 1.48 and up.
30
+// With this option set, the image inspect operation response will have the
31
+// [image.InspectResponse.Manifests] field populated if the server is multi-platform capable.
32
+func ImageInspectWithManifests(manifests bool) ImageInspectOption {
33
+	return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error {
34
+		clientOpts.apiOptions.Manifests = manifests
35
+		return nil
36
+	})
37
+}
38
+
39
+// ImageInspectWithPlatform sets platform API option for the image inspect operation.
40
+// This option is only available for API version 1.49 and up.
41
+// With this option set, the image inspect operation will return information for the
42
+// specified platform variant of the multi-platform image.
43
+func ImageInspectWithPlatform(platform *ocispec.Platform) ImageInspectOption {
44
+	return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error {
45
+		clientOpts.apiOptions.Platform = platform
46
+		return nil
47
+	})
48
+}
49
+
50
+// ImageInspectWithAPIOpts sets the API options for the image inspect operation.
51
+func ImageInspectWithAPIOpts(opts image.InspectOptions) ImageInspectOption {
52
+	return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error {
53
+		clientOpts.apiOptions = opts
54
+		return nil
55
+	})
56
+}
57
+
58
+type imageInspectOpts struct {
59
+	raw        *bytes.Buffer
60
+	apiOptions image.InspectOptions
61
+}
0 62
new file mode 100644
... ...
@@ -0,0 +1,67 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types/filters"
8
+	"github.com/moby/moby/api/types/image"
9
+	"github.com/moby/moby/api/types/versions"
10
+)
11
+
12
+// ImageList returns a list of images in the docker host.
13
+//
14
+// Experimental: Setting the [options.Manifest] will populate
15
+// [image.Summary.Manifests] with information about image manifests.
16
+// This is experimental and might change in the future without any backward
17
+// compatibility.
18
+func (cli *Client) ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error) {
19
+	var images []image.Summary
20
+
21
+	// Make sure we negotiated (if the client is configured to do so),
22
+	// as code below contains API-version specific handling of options.
23
+	//
24
+	// Normally, version-negotiation (if enabled) would not happen until
25
+	// the API request is made.
26
+	if err := cli.checkVersion(ctx); err != nil {
27
+		return images, err
28
+	}
29
+
30
+	query := url.Values{}
31
+
32
+	optionFilters := options.Filters
33
+	referenceFilters := optionFilters.Get("reference")
34
+	if versions.LessThan(cli.version, "1.25") && len(referenceFilters) > 0 {
35
+		query.Set("filter", referenceFilters[0])
36
+		for _, filterValue := range referenceFilters {
37
+			optionFilters.Del("reference", filterValue)
38
+		}
39
+	}
40
+	if optionFilters.Len() > 0 {
41
+		//nolint:staticcheck // ignore SA1019 for old code
42
+		filterJSON, err := filters.ToParamWithVersion(cli.version, optionFilters)
43
+		if err != nil {
44
+			return images, err
45
+		}
46
+		query.Set("filters", filterJSON)
47
+	}
48
+	if options.All {
49
+		query.Set("all", "1")
50
+	}
51
+	if options.SharedSize && versions.GreaterThanOrEqualTo(cli.version, "1.42") {
52
+		query.Set("shared-size", "1")
53
+	}
54
+	if options.Manifests && versions.GreaterThanOrEqualTo(cli.version, "1.47") {
55
+		query.Set("manifests", "1")
56
+	}
57
+
58
+	resp, err := cli.get(ctx, "/images/json", query, nil)
59
+	defer ensureReaderClosed(resp)
60
+	if err != nil {
61
+		return images, err
62
+	}
63
+
64
+	err = json.NewDecoder(resp.Body).Decode(&images)
65
+	return images, err
66
+}
0 67
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"net/http"
6
+	"net/url"
7
+
8
+	"github.com/moby/moby/api/types/image"
9
+)
10
+
11
+// ImageLoad loads an image in the docker host from the client host.
12
+// It's up to the caller to close the io.ReadCloser in the
13
+// ImageLoadResponse returned by this function.
14
+//
15
+// Platform is an optional parameter that specifies the platform to load from
16
+// the provided multi-platform image. This is only has effect if the input image
17
+// is a multi-platform image.
18
+func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, loadOpts ...ImageLoadOption) (image.LoadResponse, error) {
19
+	var opts imageLoadOpts
20
+	for _, opt := range loadOpts {
21
+		if err := opt.Apply(&opts); err != nil {
22
+			return image.LoadResponse{}, err
23
+		}
24
+	}
25
+
26
+	query := url.Values{}
27
+	query.Set("quiet", "0")
28
+	if opts.apiOptions.Quiet {
29
+		query.Set("quiet", "1")
30
+	}
31
+	if len(opts.apiOptions.Platforms) > 0 {
32
+		if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
33
+			return image.LoadResponse{}, err
34
+		}
35
+
36
+		p, err := encodePlatforms(opts.apiOptions.Platforms...)
37
+		if err != nil {
38
+			return image.LoadResponse{}, err
39
+		}
40
+		query["platform"] = p
41
+	}
42
+
43
+	resp, err := cli.postRaw(ctx, "/images/load", query, input, http.Header{
44
+		"Content-Type": {"application/x-tar"},
45
+	})
46
+	if err != nil {
47
+		return image.LoadResponse{}, err
48
+	}
49
+	return image.LoadResponse{
50
+		Body: resp.Body,
51
+		JSON: resp.Header.Get("Content-Type") == "application/json",
52
+	}, nil
53
+}
0 54
new file mode 100644
... ...
@@ -0,0 +1,41 @@
0
+package client
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/moby/moby/api/types/image"
6
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
7
+)
8
+
9
+// ImageLoadOption is a type representing functional options for the image load operation.
10
+type ImageLoadOption interface {
11
+	Apply(*imageLoadOpts) error
12
+}
13
+type imageLoadOptionFunc func(opt *imageLoadOpts) error
14
+
15
+func (f imageLoadOptionFunc) Apply(o *imageLoadOpts) error {
16
+	return f(o)
17
+}
18
+
19
+type imageLoadOpts struct {
20
+	apiOptions image.LoadOptions
21
+}
22
+
23
+// ImageLoadWithQuiet sets the quiet option for the image load operation.
24
+func ImageLoadWithQuiet(quiet bool) ImageLoadOption {
25
+	return imageLoadOptionFunc(func(opt *imageLoadOpts) error {
26
+		opt.apiOptions.Quiet = quiet
27
+		return nil
28
+	})
29
+}
30
+
31
+// ImageLoadWithPlatforms sets the platforms to be loaded from the image.
32
+func ImageLoadWithPlatforms(platforms ...ocispec.Platform) ImageLoadOption {
33
+	return imageLoadOptionFunc(func(opt *imageLoadOpts) error {
34
+		if opt.apiOptions.Platforms != nil {
35
+			return fmt.Errorf("platforms already set to %v", opt.apiOptions.Platforms)
36
+		}
37
+		opt.apiOptions.Platforms = platforms
38
+		return nil
39
+	})
40
+}
0 41
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"fmt"
6
+
7
+	"github.com/moby/moby/api/types/filters"
8
+	"github.com/moby/moby/api/types/image"
9
+)
10
+
11
+// ImagesPrune requests the daemon to delete unused data
12
+func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (image.PruneReport, error) {
13
+	if err := cli.NewVersionError(ctx, "1.25", "image prune"); err != nil {
14
+		return image.PruneReport{}, err
15
+	}
16
+
17
+	query, err := getFiltersQuery(pruneFilters)
18
+	if err != nil {
19
+		return image.PruneReport{}, err
20
+	}
21
+
22
+	resp, err := cli.post(ctx, "/images/prune", query, nil, nil)
23
+	defer ensureReaderClosed(resp)
24
+	if err != nil {
25
+		return image.PruneReport{}, err
26
+	}
27
+
28
+	var report image.PruneReport
29
+	if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
30
+		return image.PruneReport{}, fmt.Errorf("Error retrieving disk usage: %v", err)
31
+	}
32
+
33
+	return report, nil
34
+}
0 35
new file mode 100644
... ...
@@ -0,0 +1,60 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"net/url"
6
+	"strings"
7
+
8
+	cerrdefs "github.com/containerd/errdefs"
9
+	"github.com/distribution/reference"
10
+	"github.com/moby/moby/api/types/image"
11
+)
12
+
13
+// ImagePull requests the docker host to pull an image from a remote registry.
14
+// It executes the privileged function if the operation is unauthorized
15
+// and it tries one more time.
16
+// It's up to the caller to handle the io.ReadCloser and close it properly.
17
+//
18
+// FIXME(vdemeester): there is currently used in a few way in docker/docker
19
+// - if not in trusted content, ref is used to pass the whole reference, and tag is empty
20
+// - if in trusted content, ref is used to pass the reference name, and tag for the digest
21
+func (cli *Client) ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error) {
22
+	ref, err := reference.ParseNormalizedNamed(refStr)
23
+	if err != nil {
24
+		return nil, err
25
+	}
26
+
27
+	query := url.Values{}
28
+	query.Set("fromImage", ref.Name())
29
+	if !options.All {
30
+		query.Set("tag", getAPITagFromNamedRef(ref))
31
+	}
32
+	if options.Platform != "" {
33
+		query.Set("platform", strings.ToLower(options.Platform))
34
+	}
35
+
36
+	resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth))
37
+	if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil {
38
+		resp, err = cli.tryImageCreate(ctx, query, options.PrivilegeFunc)
39
+	}
40
+	if err != nil {
41
+		return nil, err
42
+	}
43
+	return resp.Body, nil
44
+}
45
+
46
+// getAPITagFromNamedRef returns a tag from the specified reference.
47
+// This function is necessary as long as the docker "server" api expects
48
+// digests to be sent as tags and makes a distinction between the name
49
+// and tag/digest part of a reference.
50
+func getAPITagFromNamedRef(ref reference.Named) string {
51
+	if digested, ok := ref.(reference.Digested); ok {
52
+		return digested.Digest().String()
53
+	}
54
+	ref = reference.TagNameOnly(ref)
55
+	if tagged, ok := ref.(reference.Tagged); ok {
56
+		return tagged.Tag()
57
+	}
58
+	return ""
59
+}
0 60
new file mode 100644
... ...
@@ -0,0 +1,86 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"errors"
6
+	"fmt"
7
+	"io"
8
+	"net/http"
9
+	"net/url"
10
+
11
+	cerrdefs "github.com/containerd/errdefs"
12
+	"github.com/distribution/reference"
13
+	"github.com/moby/moby/api/types/image"
14
+	"github.com/moby/moby/api/types/registry"
15
+)
16
+
17
+// ImagePush requests the docker host to push an image to a remote registry.
18
+// It executes the privileged function if the operation is unauthorized
19
+// and it tries one more time.
20
+// It's up to the caller to handle the io.ReadCloser and close it properly.
21
+func (cli *Client) ImagePush(ctx context.Context, image string, options image.PushOptions) (io.ReadCloser, error) {
22
+	ref, err := reference.ParseNormalizedNamed(image)
23
+	if err != nil {
24
+		return nil, err
25
+	}
26
+
27
+	if _, isCanonical := ref.(reference.Canonical); isCanonical {
28
+		return nil, errors.New("cannot push a digest reference")
29
+	}
30
+
31
+	query := url.Values{}
32
+	if !options.All {
33
+		ref = reference.TagNameOnly(ref)
34
+		if tagged, ok := ref.(reference.Tagged); ok {
35
+			query.Set("tag", tagged.Tag())
36
+		}
37
+	}
38
+
39
+	if options.Platform != nil {
40
+		if err := cli.NewVersionError(ctx, "1.46", "platform"); err != nil {
41
+			return nil, err
42
+		}
43
+
44
+		p := *options.Platform
45
+		pJson, err := json.Marshal(p)
46
+		if err != nil {
47
+			return nil, fmt.Errorf("invalid platform: %v", err)
48
+		}
49
+
50
+		query.Set("platform", string(pJson))
51
+	}
52
+
53
+	resp, err := cli.tryImagePush(ctx, ref.Name(), query, staticAuth(options.RegistryAuth))
54
+	if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil {
55
+		resp, err = cli.tryImagePush(ctx, ref.Name(), query, options.PrivilegeFunc)
56
+	}
57
+	if err != nil {
58
+		return nil, err
59
+	}
60
+	return resp.Body, nil
61
+}
62
+
63
+func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) {
64
+	hdr := http.Header{}
65
+	if resolveAuth != nil {
66
+		registryAuth, err := resolveAuth(ctx)
67
+		if err != nil {
68
+			return nil, err
69
+		}
70
+		if registryAuth != "" {
71
+			hdr.Set(registry.AuthHeader, registryAuth)
72
+		}
73
+	}
74
+
75
+	// Always send a body (which may be an empty JSON document ("{}")) to prevent
76
+	// EOF errors on older daemons which had faulty fallback code for handling
77
+	// authentication in the body when no auth-header was set, resulting in;
78
+	//
79
+	//	Error response from daemon: bad parameters and missing X-Registry-Auth: invalid X-Registry-Auth header: EOF
80
+	//
81
+	// We use [http.NoBody], which gets marshaled to an empty JSON document.
82
+	//
83
+	// see: https://github.com/moby/moby/commit/ea29dffaa541289591aa44fa85d2a596ce860e16
84
+	return cli.post(ctx, "/images/"+imageID+"/push", query, http.NoBody, hdr)
85
+}
0 86
new file mode 100644
... ...
@@ -0,0 +1,39 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types/image"
8
+)
9
+
10
+// ImageRemove removes an image from the docker host.
11
+func (cli *Client) ImageRemove(ctx context.Context, imageID string, options image.RemoveOptions) ([]image.DeleteResponse, error) {
12
+	query := url.Values{}
13
+
14
+	if options.Force {
15
+		query.Set("force", "1")
16
+	}
17
+	if !options.PruneChildren {
18
+		query.Set("noprune", "1")
19
+	}
20
+
21
+	if len(options.Platforms) > 0 {
22
+		p, err := encodePlatforms(options.Platforms...)
23
+		if err != nil {
24
+			return nil, err
25
+		}
26
+		query["platforms"] = p
27
+	}
28
+
29
+	resp, err := cli.delete(ctx, "/images/"+imageID, query, nil)
30
+	defer ensureReaderClosed(resp)
31
+	if err != nil {
32
+		return nil, err
33
+	}
34
+
35
+	var dels []image.DeleteResponse
36
+	err = json.NewDecoder(resp.Body).Decode(&dels)
37
+	return dels, err
38
+}
0 39
new file mode 100644
... ...
@@ -0,0 +1,41 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"net/url"
6
+)
7
+
8
+// ImageSave retrieves one or more images from the docker host as an io.ReadCloser.
9
+//
10
+// Platforms is an optional parameter that specifies the platforms to save from the image.
11
+// This is only has effect if the input image is a multi-platform image.
12
+func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, saveOpts ...ImageSaveOption) (io.ReadCloser, error) {
13
+	var opts imageSaveOpts
14
+	for _, opt := range saveOpts {
15
+		if err := opt.Apply(&opts); err != nil {
16
+			return nil, err
17
+		}
18
+	}
19
+
20
+	query := url.Values{
21
+		"names": imageIDs,
22
+	}
23
+
24
+	if len(opts.apiOptions.Platforms) > 0 {
25
+		if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
26
+			return nil, err
27
+		}
28
+		p, err := encodePlatforms(opts.apiOptions.Platforms...)
29
+		if err != nil {
30
+			return nil, err
31
+		}
32
+		query["platform"] = p
33
+	}
34
+
35
+	resp, err := cli.get(ctx, "/images/get", query, nil)
36
+	if err != nil {
37
+		return nil, err
38
+	}
39
+	return resp.Body, nil
40
+}
0 41
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+package client
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/moby/moby/api/types/image"
6
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
7
+)
8
+
9
+type ImageSaveOption interface {
10
+	Apply(*imageSaveOpts) error
11
+}
12
+
13
+type imageSaveOptionFunc func(opt *imageSaveOpts) error
14
+
15
+func (f imageSaveOptionFunc) Apply(o *imageSaveOpts) error {
16
+	return f(o)
17
+}
18
+
19
+// ImageSaveWithPlatforms sets the platforms to be saved from the image.
20
+func ImageSaveWithPlatforms(platforms ...ocispec.Platform) ImageSaveOption {
21
+	return imageSaveOptionFunc(func(opt *imageSaveOpts) error {
22
+		if opt.apiOptions.Platforms != nil {
23
+			return fmt.Errorf("platforms already set to %v", opt.apiOptions.Platforms)
24
+		}
25
+		opt.apiOptions.Platforms = platforms
26
+		return nil
27
+	})
28
+}
29
+
30
+type imageSaveOpts struct {
31
+	apiOptions image.SaveOptions
32
+}
0 33
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/http"
6
+	"net/url"
7
+	"strconv"
8
+
9
+	cerrdefs "github.com/containerd/errdefs"
10
+	"github.com/moby/moby/api/types/filters"
11
+	"github.com/moby/moby/api/types/registry"
12
+)
13
+
14
+// ImageSearch makes the docker host search by a term in a remote registry.
15
+// The list of results is not sorted in any fashion.
16
+func (cli *Client) ImageSearch(ctx context.Context, term string, options registry.SearchOptions) ([]registry.SearchResult, error) {
17
+	var results []registry.SearchResult
18
+	query := url.Values{}
19
+	query.Set("term", term)
20
+	if options.Limit > 0 {
21
+		query.Set("limit", strconv.Itoa(options.Limit))
22
+	}
23
+
24
+	if options.Filters.Len() > 0 {
25
+		filterJSON, err := filters.ToJSON(options.Filters)
26
+		if err != nil {
27
+			return results, err
28
+		}
29
+		query.Set("filters", filterJSON)
30
+	}
31
+
32
+	resp, err := cli.tryImageSearch(ctx, query, options.RegistryAuth)
33
+	defer ensureReaderClosed(resp)
34
+	if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil {
35
+		newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx)
36
+		if privilegeErr != nil {
37
+			return results, privilegeErr
38
+		}
39
+		resp, err = cli.tryImageSearch(ctx, query, newAuthHeader)
40
+	}
41
+	if err != nil {
42
+		return results, err
43
+	}
44
+
45
+	err = json.NewDecoder(resp.Body).Decode(&results)
46
+	return results, err
47
+}
48
+
49
+func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) {
50
+	return cli.get(ctx, "/images/search", query, http.Header{
51
+		registry.AuthHeader: {registryAuth},
52
+	})
53
+}
0 54
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+
6
+	"github.com/distribution/reference"
7
+	"github.com/pkg/errors"
8
+)
9
+
10
+// ImageTag tags an image in the docker host
11
+func (cli *Client) ImageTag(ctx context.Context, source, target string) error {
12
+	if _, err := reference.ParseAnyReference(source); err != nil {
13
+		return errors.Wrapf(err, "Error parsing reference: %q is not a valid repository/tag", source)
14
+	}
15
+
16
+	ref, err := reference.ParseNormalizedNamed(target)
17
+	if err != nil {
18
+		return errors.Wrapf(err, "Error parsing reference: %q is not a valid repository/tag", target)
19
+	}
20
+
21
+	if _, isCanonical := ref.(reference.Canonical); isCanonical {
22
+		return errors.New("refusing to create a tag with a digest reference")
23
+	}
24
+
25
+	ref = reference.TagNameOnly(ref)
26
+
27
+	query := url.Values{}
28
+	query.Set("repo", ref.Name())
29
+	if tagged, ok := ref.(reference.Tagged); ok {
30
+		query.Set("tag", tagged.Tag())
31
+	}
32
+
33
+	resp, err := cli.post(ctx, "/images/"+source+"/tag", query, nil, nil)
34
+	ensureReaderClosed(resp)
35
+	return err
36
+}
0 37
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"fmt"
6
+	"net/url"
7
+
8
+	"github.com/moby/moby/api/types/system"
9
+)
10
+
11
+// Info returns information about the docker server.
12
+func (cli *Client) Info(ctx context.Context) (system.Info, error) {
13
+	var info system.Info
14
+	resp, err := cli.get(ctx, "/info", url.Values{}, nil)
15
+	defer ensureReaderClosed(resp)
16
+	if err != nil {
17
+		return info, err
18
+	}
19
+
20
+	if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
21
+		return info, fmt.Errorf("Error reading remote info: %v", err)
22
+	}
23
+
24
+	return info, nil
25
+}
0 26
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types/registry"
8
+)
9
+
10
+// RegistryLogin authenticates the docker server with a given docker registry.
11
+// It returns unauthorizedError when the authentication fails.
12
+func (cli *Client) RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error) {
13
+	resp, err := cli.post(ctx, "/auth", url.Values{}, auth, nil)
14
+	defer ensureReaderClosed(resp)
15
+
16
+	if err != nil {
17
+		return registry.AuthenticateOKBody{}, err
18
+	}
19
+
20
+	var response registry.AuthenticateOKBody
21
+	err = json.NewDecoder(resp.Body).Decode(&response)
22
+	return response, err
23
+}
0 24
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/moby/moby/api/types/network"
6
+)
7
+
8
+// NetworkConnect connects a container to an existent network in the docker host.
9
+func (cli *Client) NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error {
10
+	networkID, err := trimID("network", networkID)
11
+	if err != nil {
12
+		return err
13
+	}
14
+
15
+	containerID, err = trimID("container", containerID)
16
+	if err != nil {
17
+		return err
18
+	}
19
+
20
+	nc := network.ConnectOptions{
21
+		Container:      containerID,
22
+		EndpointConfig: config,
23
+	}
24
+	resp, err := cli.post(ctx, "/networks/"+networkID+"/connect", nil, nc, nil)
25
+	ensureReaderClosed(resp)
26
+	return err
27
+}
0 28
new file mode 100644
... ...
@@ -0,0 +1,40 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+
6
+	"github.com/moby/moby/api/types/network"
7
+	"github.com/moby/moby/api/types/versions"
8
+)
9
+
10
+// NetworkCreate creates a new network in the docker host.
11
+func (cli *Client) NetworkCreate(ctx context.Context, name string, options network.CreateOptions) (network.CreateResponse, error) {
12
+	// Make sure we negotiated (if the client is configured to do so),
13
+	// as code below contains API-version specific handling of options.
14
+	//
15
+	// Normally, version-negotiation (if enabled) would not happen until
16
+	// the API request is made.
17
+	if err := cli.checkVersion(ctx); err != nil {
18
+		return network.CreateResponse{}, err
19
+	}
20
+
21
+	networkCreateRequest := network.CreateRequest{
22
+		CreateOptions: options,
23
+		Name:          name,
24
+	}
25
+	if versions.LessThan(cli.version, "1.44") {
26
+		enabled := true
27
+		networkCreateRequest.CheckDuplicate = &enabled //nolint:staticcheck // ignore SA1019: CheckDuplicate is deprecated since API v1.44.
28
+	}
29
+
30
+	resp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil)
31
+	defer ensureReaderClosed(resp)
32
+	if err != nil {
33
+		return network.CreateResponse{}, err
34
+	}
35
+
36
+	var response network.CreateResponse
37
+	err = json.NewDecoder(resp.Body).Decode(&response)
38
+	return response, err
39
+}
0 40
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/moby/moby/api/types/network"
6
+)
7
+
8
+// NetworkDisconnect disconnects a container from an existent network in the docker host.
9
+func (cli *Client) NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error {
10
+	networkID, err := trimID("network", networkID)
11
+	if err != nil {
12
+		return err
13
+	}
14
+
15
+	containerID, err = trimID("container", containerID)
16
+	if err != nil {
17
+		return err
18
+	}
19
+
20
+	nd := network.DisconnectOptions{
21
+		Container: containerID,
22
+		Force:     force,
23
+	}
24
+	resp, err := cli.post(ctx, "/networks/"+networkID+"/disconnect", nil, nd, nil)
25
+	ensureReaderClosed(resp)
26
+	return err
27
+}
0 28
new file mode 100644
... ...
@@ -0,0 +1,47 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"context"
5
+	"encoding/json"
6
+	"io"
7
+	"net/url"
8
+
9
+	"github.com/moby/moby/api/types/network"
10
+)
11
+
12
+// NetworkInspect returns the information for a specific network configured in the docker host.
13
+func (cli *Client) NetworkInspect(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, error) {
14
+	networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID, options)
15
+	return networkResource, err
16
+}
17
+
18
+// NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation.
19
+func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, []byte, error) {
20
+	networkID, err := trimID("network", networkID)
21
+	if err != nil {
22
+		return network.Inspect{}, nil, err
23
+	}
24
+	query := url.Values{}
25
+	if options.Verbose {
26
+		query.Set("verbose", "true")
27
+	}
28
+	if options.Scope != "" {
29
+		query.Set("scope", options.Scope)
30
+	}
31
+
32
+	resp, err := cli.get(ctx, "/networks/"+networkID, query, nil)
33
+	defer ensureReaderClosed(resp)
34
+	if err != nil {
35
+		return network.Inspect{}, nil, err
36
+	}
37
+
38
+	raw, err := io.ReadAll(resp.Body)
39
+	if err != nil {
40
+		return network.Inspect{}, nil, err
41
+	}
42
+
43
+	var nw network.Inspect
44
+	err = json.NewDecoder(bytes.NewReader(raw)).Decode(&nw)
45
+	return nw, raw, err
46
+}
0 47
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types/filters"
8
+	"github.com/moby/moby/api/types/network"
9
+)
10
+
11
+// NetworkList returns the list of networks configured in the docker host.
12
+func (cli *Client) NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
13
+	query := url.Values{}
14
+	if options.Filters.Len() > 0 {
15
+		//nolint:staticcheck // ignore SA1019 for old code
16
+		filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
17
+		if err != nil {
18
+			return nil, err
19
+		}
20
+
21
+		query.Set("filters", filterJSON)
22
+	}
23
+	var networkResources []network.Summary
24
+	resp, err := cli.get(ctx, "/networks", query, nil)
25
+	defer ensureReaderClosed(resp)
26
+	if err != nil {
27
+		return networkResources, err
28
+	}
29
+	err = json.NewDecoder(resp.Body).Decode(&networkResources)
30
+	return networkResources, err
31
+}
0 32
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"fmt"
6
+
7
+	"github.com/moby/moby/api/types/filters"
8
+	"github.com/moby/moby/api/types/network"
9
+)
10
+
11
+// NetworksPrune requests the daemon to delete unused networks
12
+func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error) {
13
+	if err := cli.NewVersionError(ctx, "1.25", "network prune"); err != nil {
14
+		return network.PruneReport{}, err
15
+	}
16
+
17
+	query, err := getFiltersQuery(pruneFilters)
18
+	if err != nil {
19
+		return network.PruneReport{}, err
20
+	}
21
+
22
+	resp, err := cli.post(ctx, "/networks/prune", query, nil, nil)
23
+	defer ensureReaderClosed(resp)
24
+	if err != nil {
25
+		return network.PruneReport{}, err
26
+	}
27
+
28
+	var report network.PruneReport
29
+	if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
30
+		return network.PruneReport{}, fmt.Errorf("Error retrieving network prune report: %v", err)
31
+	}
32
+
33
+	return report, nil
34
+}
0 35
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+package client
1
+
2
+import "context"
3
+
4
+// NetworkRemove removes an existent network from the docker host.
5
+func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error {
6
+	networkID, err := trimID("network", networkID)
7
+	if err != nil {
8
+		return err
9
+	}
10
+	resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil)
11
+	defer ensureReaderClosed(resp)
12
+	return err
13
+}
0 14
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"context"
5
+	"encoding/json"
6
+	"io"
7
+
8
+	"github.com/moby/moby/api/types/swarm"
9
+)
10
+
11
+// NodeInspectWithRaw returns the node information.
12
+func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) {
13
+	nodeID, err := trimID("node", nodeID)
14
+	if err != nil {
15
+		return swarm.Node{}, nil, err
16
+	}
17
+	resp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
18
+	defer ensureReaderClosed(resp)
19
+	if err != nil {
20
+		return swarm.Node{}, nil, err
21
+	}
22
+
23
+	body, err := io.ReadAll(resp.Body)
24
+	if err != nil {
25
+		return swarm.Node{}, nil, err
26
+	}
27
+
28
+	var response swarm.Node
29
+	rdr := bytes.NewReader(body)
30
+	err = json.NewDecoder(rdr).Decode(&response)
31
+	return response, body, err
32
+}
0 33
new file mode 100644
... ...
@@ -0,0 +1,34 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types/filters"
8
+	"github.com/moby/moby/api/types/swarm"
9
+)
10
+
11
+// NodeList returns the list of nodes.
12
+func (cli *Client) NodeList(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
13
+	query := url.Values{}
14
+
15
+	if options.Filters.Len() > 0 {
16
+		filterJSON, err := filters.ToJSON(options.Filters)
17
+		if err != nil {
18
+			return nil, err
19
+		}
20
+
21
+		query.Set("filters", filterJSON)
22
+	}
23
+
24
+	resp, err := cli.get(ctx, "/nodes", query, nil)
25
+	defer ensureReaderClosed(resp)
26
+	if err != nil {
27
+		return nil, err
28
+	}
29
+
30
+	var nodes []swarm.Node
31
+	err = json.NewDecoder(resp.Body).Decode(&nodes)
32
+	return nodes, err
33
+}
0 34
new file mode 100644
... ...
@@ -0,0 +1,25 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+
6
+	"github.com/moby/moby/api/types/swarm"
7
+)
8
+
9
+// NodeRemove removes a Node.
10
+func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options swarm.NodeRemoveOptions) error {
11
+	nodeID, err := trimID("node", nodeID)
12
+	if err != nil {
13
+		return err
14
+	}
15
+
16
+	query := url.Values{}
17
+	if options.Force {
18
+		query.Set("force", "1")
19
+	}
20
+
21
+	resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil)
22
+	defer ensureReaderClosed(resp)
23
+	return err
24
+}
0 25
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+
6
+	"github.com/moby/moby/api/types/swarm"
7
+)
8
+
9
+// NodeUpdate updates a Node.
10
+func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error {
11
+	nodeID, err := trimID("node", nodeID)
12
+	if err != nil {
13
+		return err
14
+	}
15
+
16
+	query := url.Values{}
17
+	query.Set("version", version.String())
18
+	resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, node, nil)
19
+	ensureReaderClosed(resp)
20
+	return err
21
+}
0 22
new file mode 100644
... ...
@@ -0,0 +1,248 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net"
5
+	"net/http"
6
+	"os"
7
+	"path/filepath"
8
+	"strings"
9
+	"time"
10
+
11
+	"github.com/docker/go-connections/sockets"
12
+	"github.com/docker/go-connections/tlsconfig"
13
+	"github.com/pkg/errors"
14
+	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
15
+	"go.opentelemetry.io/otel/trace"
16
+)
17
+
18
+// Opt is a configuration option to initialize a [Client].
19
+type Opt func(*Client) error
20
+
21
+// FromEnv configures the client with values from environment variables. It
22
+// is the equivalent of using the [WithTLSClientConfigFromEnv], [WithHostFromEnv],
23
+// and [WithVersionFromEnv] options.
24
+//
25
+// FromEnv uses the following environment variables:
26
+//
27
+//   - DOCKER_HOST ([EnvOverrideHost]) to set the URL to the docker server.
28
+//   - DOCKER_API_VERSION ([EnvOverrideAPIVersion]) to set the version of the
29
+//     API to use, leave empty for latest.
30
+//   - DOCKER_CERT_PATH ([EnvOverrideCertPath]) to specify the directory from
31
+//     which to load the TLS certificates ("ca.pem", "cert.pem", "key.pem').
32
+//   - DOCKER_TLS_VERIFY ([EnvTLSVerify]) to enable or disable TLS verification
33
+//     (off by default).
34
+func FromEnv(c *Client) error {
35
+	ops := []Opt{
36
+		WithTLSClientConfigFromEnv(),
37
+		WithHostFromEnv(),
38
+		WithVersionFromEnv(),
39
+	}
40
+	for _, op := range ops {
41
+		if err := op(c); err != nil {
42
+			return err
43
+		}
44
+	}
45
+	return nil
46
+}
47
+
48
+// WithDialContext applies the dialer to the client transport. This can be
49
+// used to set the Timeout and KeepAlive settings of the client. It returns
50
+// an error if the client does not have a [http.Transport] configured.
51
+func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) Opt {
52
+	return func(c *Client) error {
53
+		if transport, ok := c.client.Transport.(*http.Transport); ok {
54
+			transport.DialContext = dialContext
55
+			return nil
56
+		}
57
+		return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport)
58
+	}
59
+}
60
+
61
+// WithHost overrides the client host with the specified one.
62
+func WithHost(host string) Opt {
63
+	return func(c *Client) error {
64
+		hostURL, err := ParseHostURL(host)
65
+		if err != nil {
66
+			return err
67
+		}
68
+		c.host = host
69
+		c.proto = hostURL.Scheme
70
+		c.addr = hostURL.Host
71
+		c.basePath = hostURL.Path
72
+		if transport, ok := c.client.Transport.(*http.Transport); ok {
73
+			return sockets.ConfigureTransport(transport, c.proto, c.addr)
74
+		}
75
+		return errors.Errorf("cannot apply host to transport: %T", c.client.Transport)
76
+	}
77
+}
78
+
79
+// WithHostFromEnv overrides the client host with the host specified in the
80
+// DOCKER_HOST ([EnvOverrideHost]) environment variable. If DOCKER_HOST is not set,
81
+// or set to an empty value, the host is not modified.
82
+func WithHostFromEnv() Opt {
83
+	return func(c *Client) error {
84
+		if host := os.Getenv(EnvOverrideHost); host != "" {
85
+			return WithHost(host)(c)
86
+		}
87
+		return nil
88
+	}
89
+}
90
+
91
+// WithHTTPClient overrides the client's HTTP client with the specified one.
92
+func WithHTTPClient(client *http.Client) Opt {
93
+	return func(c *Client) error {
94
+		if client != nil {
95
+			c.client = client
96
+		}
97
+		return nil
98
+	}
99
+}
100
+
101
+// WithTimeout configures the time limit for requests made by the HTTP client.
102
+func WithTimeout(timeout time.Duration) Opt {
103
+	return func(c *Client) error {
104
+		c.client.Timeout = timeout
105
+		return nil
106
+	}
107
+}
108
+
109
+// WithUserAgent configures the User-Agent header to use for HTTP requests.
110
+// It overrides any User-Agent set in headers. When set to an empty string,
111
+// the User-Agent header is removed, and no header is sent.
112
+func WithUserAgent(ua string) Opt {
113
+	return func(c *Client) error {
114
+		c.userAgent = &ua
115
+		return nil
116
+	}
117
+}
118
+
119
+// WithHTTPHeaders appends custom HTTP headers to the client's default headers.
120
+// It does not allow for built-in headers (such as "User-Agent", if set) to
121
+// be overridden. Also see [WithUserAgent].
122
+func WithHTTPHeaders(headers map[string]string) Opt {
123
+	return func(c *Client) error {
124
+		c.customHTTPHeaders = headers
125
+		return nil
126
+	}
127
+}
128
+
129
+// WithScheme overrides the client scheme with the specified one.
130
+func WithScheme(scheme string) Opt {
131
+	return func(c *Client) error {
132
+		c.scheme = scheme
133
+		return nil
134
+	}
135
+}
136
+
137
+// WithTLSClientConfig applies a TLS config to the client transport.
138
+func WithTLSClientConfig(cacertPath, certPath, keyPath string) Opt {
139
+	return func(c *Client) error {
140
+		transport, ok := c.client.Transport.(*http.Transport)
141
+		if !ok {
142
+			return errors.Errorf("cannot apply tls config to transport: %T", c.client.Transport)
143
+		}
144
+		config, err := tlsconfig.Client(tlsconfig.Options{
145
+			CAFile:             cacertPath,
146
+			CertFile:           certPath,
147
+			KeyFile:            keyPath,
148
+			ExclusiveRootPools: true,
149
+		})
150
+		if err != nil {
151
+			return errors.Wrap(err, "failed to create tls config")
152
+		}
153
+		transport.TLSClientConfig = config
154
+		return nil
155
+	}
156
+}
157
+
158
+// WithTLSClientConfigFromEnv configures the client's TLS settings with the
159
+// settings in the DOCKER_CERT_PATH ([EnvOverrideCertPath]) and DOCKER_TLS_VERIFY
160
+// ([EnvTLSVerify]) environment variables. If DOCKER_CERT_PATH is not set or empty,
161
+// TLS configuration is not modified.
162
+//
163
+// WithTLSClientConfigFromEnv uses the following environment variables:
164
+//
165
+//   - DOCKER_CERT_PATH ([EnvOverrideCertPath]) to specify the directory from
166
+//     which to load the TLS certificates ("ca.pem", "cert.pem", "key.pem").
167
+//   - DOCKER_TLS_VERIFY ([EnvTLSVerify]) to enable or disable TLS verification
168
+//     (off by default).
169
+func WithTLSClientConfigFromEnv() Opt {
170
+	return func(c *Client) error {
171
+		dockerCertPath := os.Getenv(EnvOverrideCertPath)
172
+		if dockerCertPath == "" {
173
+			return nil
174
+		}
175
+		tlsc, err := tlsconfig.Client(tlsconfig.Options{
176
+			CAFile:             filepath.Join(dockerCertPath, "ca.pem"),
177
+			CertFile:           filepath.Join(dockerCertPath, "cert.pem"),
178
+			KeyFile:            filepath.Join(dockerCertPath, "key.pem"),
179
+			InsecureSkipVerify: os.Getenv(EnvTLSVerify) == "",
180
+		})
181
+		if err != nil {
182
+			return err
183
+		}
184
+
185
+		c.client = &http.Client{
186
+			Transport:     &http.Transport{TLSClientConfig: tlsc},
187
+			CheckRedirect: CheckRedirect,
188
+		}
189
+		return nil
190
+	}
191
+}
192
+
193
+// WithVersion overrides the client version with the specified one. If an empty
194
+// version is provided, the value is ignored to allow version negotiation
195
+// (see [WithAPIVersionNegotiation]).
196
+//
197
+// WithVersion does not validate if the client supports the given version,
198
+// and callers should verify if the version is in the correct format and
199
+// lower than the maximum supported version as defined by [DefaultAPIVersion].
200
+func WithVersion(version string) Opt {
201
+	return func(c *Client) error {
202
+		if v := strings.TrimPrefix(version, "v"); v != "" {
203
+			c.version = v
204
+			c.manualOverride = true
205
+		}
206
+		return nil
207
+	}
208
+}
209
+
210
+// WithVersionFromEnv overrides the client version with the version specified in
211
+// the DOCKER_API_VERSION ([EnvOverrideAPIVersion]) environment variable.
212
+// If DOCKER_API_VERSION is not set, or set to an empty value, the version
213
+// is not modified.
214
+//
215
+// WithVersion does not validate if the client supports the given version,
216
+// and callers should verify if the version is in the correct format and
217
+// lower than the maximum supported version as defined by [DefaultAPIVersion].
218
+func WithVersionFromEnv() Opt {
219
+	return func(c *Client) error {
220
+		return WithVersion(os.Getenv(EnvOverrideAPIVersion))(c)
221
+	}
222
+}
223
+
224
+// WithAPIVersionNegotiation enables automatic API version negotiation for the client.
225
+// With this option enabled, the client automatically negotiates the API version
226
+// to use when making requests. API version negotiation is performed on the first
227
+// request; subsequent requests do not re-negotiate.
228
+func WithAPIVersionNegotiation() Opt {
229
+	return func(c *Client) error {
230
+		c.negotiateVersion = true
231
+		return nil
232
+	}
233
+}
234
+
235
+// WithTraceProvider sets the trace provider for the client.
236
+// If this is not set then the global trace provider will be used.
237
+func WithTraceProvider(provider trace.TracerProvider) Opt {
238
+	return WithTraceOptions(otelhttp.WithTracerProvider(provider))
239
+}
240
+
241
+// WithTraceOptions sets tracing span options for the client.
242
+func WithTraceOptions(opts ...otelhttp.Option) Opt {
243
+	return func(c *Client) error {
244
+		c.traceOpts = append(c.traceOpts, opts...)
245
+		return nil
246
+	}
247
+}
0 248
new file mode 100644
... ...
@@ -0,0 +1,81 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/http"
5
+	"path"
6
+	"strings"
7
+
8
+	"github.com/moby/moby/api/types"
9
+	"github.com/moby/moby/api/types/build"
10
+	"github.com/moby/moby/api/types/swarm"
11
+)
12
+
13
+// Ping pings the server and returns the value of the "Docker-Experimental",
14
+// "Builder-Version", "OS-Type" & "API-Version" headers. It attempts to use
15
+// a HEAD request on the endpoint, but falls back to GET if HEAD is not supported
16
+// by the daemon. It ignores internal server errors returned by the API, which
17
+// may be returned if the daemon is in an unhealthy state, but returns errors
18
+// for other non-success status codes, failing to connect to the API, or failing
19
+// to parse the API response.
20
+func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
21
+	var ping types.Ping
22
+
23
+	// Using cli.buildRequest() + cli.doRequest() instead of cli.sendRequest()
24
+	// because ping requests are used during API version negotiation, so we want
25
+	// to hit the non-versioned /_ping endpoint, not /v1.xx/_ping
26
+	req, err := cli.buildRequest(ctx, http.MethodHead, path.Join(cli.basePath, "/_ping"), nil, nil)
27
+	if err != nil {
28
+		return ping, err
29
+	}
30
+	resp, err := cli.doRequest(req)
31
+	if err != nil {
32
+		if IsErrConnectionFailed(err) {
33
+			return ping, err
34
+		}
35
+		// We managed to connect, but got some error; continue and try GET request.
36
+	} else {
37
+		defer ensureReaderClosed(resp)
38
+		switch resp.StatusCode {
39
+		case http.StatusOK, http.StatusInternalServerError:
40
+			// Server handled the request, so parse the response
41
+			return parsePingResponse(cli, resp)
42
+		}
43
+	}
44
+
45
+	// HEAD failed; fallback to GET.
46
+	req.Method = http.MethodGet
47
+	resp, err = cli.doRequest(req)
48
+	defer ensureReaderClosed(resp)
49
+	if err != nil {
50
+		return ping, err
51
+	}
52
+	return parsePingResponse(cli, resp)
53
+}
54
+
55
+func parsePingResponse(cli *Client, resp *http.Response) (types.Ping, error) {
56
+	if resp == nil {
57
+		return types.Ping{}, nil
58
+	}
59
+
60
+	var ping types.Ping
61
+	if resp.Header == nil {
62
+		return ping, cli.checkResponseErr(resp)
63
+	}
64
+	ping.APIVersion = resp.Header.Get("Api-Version")
65
+	ping.OSType = resp.Header.Get("Ostype")
66
+	if resp.Header.Get("Docker-Experimental") == "true" {
67
+		ping.Experimental = true
68
+	}
69
+	if bv := resp.Header.Get("Builder-Version"); bv != "" {
70
+		ping.BuilderVersion = build.BuilderVersion(bv)
71
+	}
72
+	if si := resp.Header.Get("Swarm"); si != "" {
73
+		state, role, _ := strings.Cut(si, "/")
74
+		ping.SwarmStatus = &swarm.Status{
75
+			NodeState:        swarm.LocalNodeState(state),
76
+			ControlAvailable: role == "manager",
77
+		}
78
+	}
79
+	return ping, cli.checkResponseErr(resp)
80
+}
0 81
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"net/http"
6
+	"net/url"
7
+
8
+	"github.com/moby/moby/api/types"
9
+)
10
+
11
+// PluginCreate creates a plugin
12
+func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, createOptions types.PluginCreateOptions) error {
13
+	headers := http.Header(make(map[string][]string))
14
+	headers.Set("Content-Type", "application/x-tar")
15
+
16
+	query := url.Values{}
17
+	query.Set("name", createOptions.RepoName)
18
+
19
+	resp, err := cli.postRaw(ctx, "/plugins/create", query, createContext, headers)
20
+	ensureReaderClosed(resp)
21
+	return err
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+
6
+	"github.com/moby/moby/api/types"
7
+)
8
+
9
+// PluginDisable disables a plugin
10
+func (cli *Client) PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error {
11
+	name, err := trimID("plugin", name)
12
+	if err != nil {
13
+		return err
14
+	}
15
+	query := url.Values{}
16
+	if options.Force {
17
+		query.Set("force", "1")
18
+	}
19
+	resp, err := cli.post(ctx, "/plugins/"+name+"/disable", query, nil, nil)
20
+	ensureReaderClosed(resp)
21
+	return err
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+	"strconv"
6
+
7
+	"github.com/moby/moby/api/types"
8
+)
9
+
10
+// PluginEnable enables a plugin
11
+func (cli *Client) PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error {
12
+	name, err := trimID("plugin", name)
13
+	if err != nil {
14
+		return err
15
+	}
16
+	query := url.Values{}
17
+	query.Set("timeout", strconv.Itoa(options.Timeout))
18
+
19
+	resp, err := cli.post(ctx, "/plugins/"+name+"/enable", query, nil, nil)
20
+	ensureReaderClosed(resp)
21
+	return err
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"context"
5
+	"encoding/json"
6
+	"io"
7
+
8
+	"github.com/moby/moby/api/types"
9
+)
10
+
11
+// PluginInspectWithRaw inspects an existing plugin
12
+func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) {
13
+	name, err := trimID("plugin", name)
14
+	if err != nil {
15
+		return nil, nil, err
16
+	}
17
+	resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil)
18
+	defer ensureReaderClosed(resp)
19
+	if err != nil {
20
+		return nil, nil, err
21
+	}
22
+
23
+	body, err := io.ReadAll(resp.Body)
24
+	if err != nil {
25
+		return nil, nil, err
26
+	}
27
+	var p types.Plugin
28
+	rdr := bytes.NewReader(body)
29
+	err = json.NewDecoder(rdr).Decode(&p)
30
+	return &p, body, err
31
+}
0 32
new file mode 100644
... ...
@@ -0,0 +1,117 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"io"
6
+	"net/http"
7
+	"net/url"
8
+
9
+	cerrdefs "github.com/containerd/errdefs"
10
+	"github.com/distribution/reference"
11
+	"github.com/moby/moby/api/types"
12
+	"github.com/moby/moby/api/types/registry"
13
+	"github.com/pkg/errors"
14
+)
15
+
16
+// PluginInstall installs a plugin
17
+func (cli *Client) PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) (_ io.ReadCloser, retErr error) {
18
+	query := url.Values{}
19
+	if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil {
20
+		return nil, errors.Wrap(err, "invalid remote reference")
21
+	}
22
+	query.Set("remote", options.RemoteRef)
23
+
24
+	privileges, err := cli.checkPluginPermissions(ctx, query, options)
25
+	if err != nil {
26
+		return nil, err
27
+	}
28
+
29
+	// set name for plugin pull, if empty should default to remote reference
30
+	query.Set("name", name)
31
+
32
+	resp, err := cli.tryPluginPull(ctx, query, privileges, options.RegistryAuth)
33
+	if err != nil {
34
+		return nil, err
35
+	}
36
+
37
+	name = resp.Header.Get("Docker-Plugin-Name")
38
+
39
+	pr, pw := io.Pipe()
40
+	go func() { // todo: the client should probably be designed more around the actual api
41
+		_, err := io.Copy(pw, resp.Body)
42
+		if err != nil {
43
+			_ = pw.CloseWithError(err)
44
+			return
45
+		}
46
+		defer func() {
47
+			if retErr != nil {
48
+				delResp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil)
49
+				ensureReaderClosed(delResp)
50
+			}
51
+		}()
52
+		if len(options.Args) > 0 {
53
+			if err := cli.PluginSet(ctx, name, options.Args); err != nil {
54
+				_ = pw.CloseWithError(err)
55
+				return
56
+			}
57
+		}
58
+
59
+		if options.Disabled {
60
+			_ = pw.Close()
61
+			return
62
+		}
63
+
64
+		enableErr := cli.PluginEnable(ctx, name, types.PluginEnableOptions{Timeout: 0})
65
+		_ = pw.CloseWithError(enableErr)
66
+	}()
67
+	return pr, nil
68
+}
69
+
70
+func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) {
71
+	return cli.get(ctx, "/plugins/privileges", query, http.Header{
72
+		registry.AuthHeader: {registryAuth},
73
+	})
74
+}
75
+
76
+func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileges types.PluginPrivileges, registryAuth string) (*http.Response, error) {
77
+	return cli.post(ctx, "/plugins/pull", query, privileges, http.Header{
78
+		registry.AuthHeader: {registryAuth},
79
+	})
80
+}
81
+
82
+func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, options types.PluginInstallOptions) (types.PluginPrivileges, error) {
83
+	resp, err := cli.tryPluginPrivileges(ctx, query, options.RegistryAuth)
84
+	if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil {
85
+		// todo: do inspect before to check existing name before checking privileges
86
+		newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx)
87
+		if privilegeErr != nil {
88
+			ensureReaderClosed(resp)
89
+			return nil, privilegeErr
90
+		}
91
+		options.RegistryAuth = newAuthHeader
92
+		resp, err = cli.tryPluginPrivileges(ctx, query, options.RegistryAuth)
93
+	}
94
+	if err != nil {
95
+		ensureReaderClosed(resp)
96
+		return nil, err
97
+	}
98
+
99
+	var privileges types.PluginPrivileges
100
+	if err := json.NewDecoder(resp.Body).Decode(&privileges); err != nil {
101
+		ensureReaderClosed(resp)
102
+		return nil, err
103
+	}
104
+	ensureReaderClosed(resp)
105
+
106
+	if !options.AcceptAllPermissions && options.AcceptPermissionsFunc != nil && len(privileges) > 0 {
107
+		accept, err := options.AcceptPermissionsFunc(ctx, privileges)
108
+		if err != nil {
109
+			return nil, err
110
+		}
111
+		if !accept {
112
+			return nil, errors.Errorf("permission denied while installing plugin %s", options.RemoteRef)
113
+		}
114
+	}
115
+	return privileges, nil
116
+}
0 117
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types"
8
+	"github.com/moby/moby/api/types/filters"
9
+)
10
+
11
+// PluginList returns the installed plugins
12
+func (cli *Client) PluginList(ctx context.Context, filter filters.Args) (types.PluginsListResponse, error) {
13
+	var plugins types.PluginsListResponse
14
+	query := url.Values{}
15
+
16
+	if filter.Len() > 0 {
17
+		//nolint:staticcheck // ignore SA1019 for old code
18
+		filterJSON, err := filters.ToParamWithVersion(cli.version, filter)
19
+		if err != nil {
20
+			return plugins, err
21
+		}
22
+		query.Set("filters", filterJSON)
23
+	}
24
+	resp, err := cli.get(ctx, "/plugins", query, nil)
25
+	defer ensureReaderClosed(resp)
26
+	if err != nil {
27
+		return plugins, err
28
+	}
29
+
30
+	err = json.NewDecoder(resp.Body).Decode(&plugins)
31
+	return plugins, err
32
+}
0 33
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"net/http"
6
+
7
+	"github.com/moby/moby/api/types/registry"
8
+)
9
+
10
+// PluginPush pushes a plugin to a registry
11
+func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) {
12
+	name, err := trimID("plugin", name)
13
+	if err != nil {
14
+		return nil, err
15
+	}
16
+	resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, http.Header{
17
+		registry.AuthHeader: {registryAuth},
18
+	})
19
+	if err != nil {
20
+		return nil, err
21
+	}
22
+	return resp.Body, nil
23
+}
0 24
new file mode 100644
... ...
@@ -0,0 +1,25 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+
6
+	"github.com/moby/moby/api/types"
7
+)
8
+
9
+// PluginRemove removes a plugin
10
+func (cli *Client) PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error {
11
+	name, err := trimID("plugin", name)
12
+	if err != nil {
13
+		return err
14
+	}
15
+
16
+	query := url.Values{}
17
+	if options.Force {
18
+		query.Set("force", "1")
19
+	}
20
+
21
+	resp, err := cli.delete(ctx, "/plugins/"+name, query, nil)
22
+	defer ensureReaderClosed(resp)
23
+	return err
24
+}
0 25
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+)
5
+
6
+// PluginSet modifies settings for an existing plugin
7
+func (cli *Client) PluginSet(ctx context.Context, name string, args []string) error {
8
+	name, err := trimID("plugin", name)
9
+	if err != nil {
10
+		return err
11
+	}
12
+
13
+	resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, args, nil)
14
+	ensureReaderClosed(resp)
15
+	return err
16
+}
0 17
new file mode 100644
... ...
@@ -0,0 +1,47 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"net/http"
6
+	"net/url"
7
+
8
+	"github.com/distribution/reference"
9
+	"github.com/moby/moby/api/types"
10
+	"github.com/moby/moby/api/types/registry"
11
+	"github.com/pkg/errors"
12
+)
13
+
14
+// PluginUpgrade upgrades a plugin
15
+func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
16
+	name, err := trimID("plugin", name)
17
+	if err != nil {
18
+		return nil, err
19
+	}
20
+
21
+	if err := cli.NewVersionError(ctx, "1.26", "plugin upgrade"); err != nil {
22
+		return nil, err
23
+	}
24
+	query := url.Values{}
25
+	if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil {
26
+		return nil, errors.Wrap(err, "invalid remote reference")
27
+	}
28
+	query.Set("remote", options.RemoteRef)
29
+
30
+	privileges, err := cli.checkPluginPermissions(ctx, query, options)
31
+	if err != nil {
32
+		return nil, err
33
+	}
34
+
35
+	resp, err := cli.tryPluginUpgrade(ctx, query, privileges, name, options.RegistryAuth)
36
+	if err != nil {
37
+		return nil, err
38
+	}
39
+	return resp.Body, nil
40
+}
41
+
42
+func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privileges types.PluginPrivileges, name, registryAuth string) (*http.Response, error) {
43
+	return cli.post(ctx, "/plugins/"+name+"/upgrade", query, privileges, http.Header{
44
+		registry.AuthHeader: {registryAuth},
45
+	})
46
+}
0 47
new file mode 100644
... ...
@@ -0,0 +1,318 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"context"
5
+	"encoding/json"
6
+	"fmt"
7
+	"io"
8
+	"net"
9
+	"net/http"
10
+	"net/url"
11
+	"os"
12
+	"reflect"
13
+	"strings"
14
+
15
+	"github.com/moby/moby/api/types"
16
+	"github.com/pkg/errors"
17
+)
18
+
19
+// head sends an http request to the docker API using the method HEAD.
20
+func (cli *Client) head(ctx context.Context, path string, query url.Values, headers http.Header) (*http.Response, error) {
21
+	return cli.sendRequest(ctx, http.MethodHead, path, query, nil, headers)
22
+}
23
+
24
+// get sends an http request to the docker API using the method GET with a specific Go context.
25
+func (cli *Client) get(ctx context.Context, path string, query url.Values, headers http.Header) (*http.Response, error) {
26
+	return cli.sendRequest(ctx, http.MethodGet, path, query, nil, headers)
27
+}
28
+
29
+// post sends an http request to the docker API using the method POST with a specific Go context.
30
+func (cli *Client) post(ctx context.Context, path string, query url.Values, obj interface{}, headers http.Header) (*http.Response, error) {
31
+	body, headers, err := encodeBody(obj, headers)
32
+	if err != nil {
33
+		return nil, err
34
+	}
35
+	return cli.sendRequest(ctx, http.MethodPost, path, query, body, headers)
36
+}
37
+
38
+func (cli *Client) postRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers http.Header) (*http.Response, error) {
39
+	return cli.sendRequest(ctx, http.MethodPost, path, query, body, headers)
40
+}
41
+
42
+func (cli *Client) put(ctx context.Context, path string, query url.Values, obj interface{}, headers http.Header) (*http.Response, error) {
43
+	body, headers, err := encodeBody(obj, headers)
44
+	if err != nil {
45
+		return nil, err
46
+	}
47
+	return cli.putRaw(ctx, path, query, body, headers)
48
+}
49
+
50
+// putRaw sends an http request to the docker API using the method PUT.
51
+func (cli *Client) putRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers http.Header) (*http.Response, error) {
52
+	// PUT requests are expected to always have a body (apparently)
53
+	// so explicitly pass an empty body to sendRequest to signal that
54
+	// it should set the Content-Type header if not already present.
55
+	if body == nil {
56
+		body = http.NoBody
57
+	}
58
+	return cli.sendRequest(ctx, http.MethodPut, path, query, body, headers)
59
+}
60
+
61
+// delete sends an http request to the docker API using the method DELETE.
62
+func (cli *Client) delete(ctx context.Context, path string, query url.Values, headers http.Header) (*http.Response, error) {
63
+	return cli.sendRequest(ctx, http.MethodDelete, path, query, nil, headers)
64
+}
65
+
66
+func encodeBody(obj interface{}, headers http.Header) (io.Reader, http.Header, error) {
67
+	if obj == nil {
68
+		return nil, headers, nil
69
+	}
70
+	// encoding/json encodes a nil pointer as the JSON document `null`,
71
+	// irrespective of whether the type implements json.Marshaler or encoding.TextMarshaler.
72
+	// That is almost certainly not what the caller intended as the request body.
73
+	if reflect.TypeOf(obj).Kind() == reflect.Ptr && reflect.ValueOf(obj).IsNil() {
74
+		return nil, headers, nil
75
+	}
76
+
77
+	body, err := encodeData(obj)
78
+	if err != nil {
79
+		return nil, headers, err
80
+	}
81
+	if headers == nil {
82
+		headers = make(map[string][]string)
83
+	}
84
+	headers["Content-Type"] = []string{"application/json"}
85
+	return body, headers, nil
86
+}
87
+
88
+func (cli *Client) buildRequest(ctx context.Context, method, path string, body io.Reader, headers http.Header) (*http.Request, error) {
89
+	req, err := http.NewRequestWithContext(ctx, method, path, body)
90
+	if err != nil {
91
+		return nil, err
92
+	}
93
+	req = cli.addHeaders(req, headers)
94
+	req.URL.Scheme = cli.scheme
95
+	req.URL.Host = cli.addr
96
+
97
+	if cli.proto == "unix" || cli.proto == "npipe" {
98
+		// Override host header for non-tcp connections.
99
+		req.Host = DummyHost
100
+	}
101
+
102
+	if body != nil && req.Header.Get("Content-Type") == "" {
103
+		req.Header.Set("Content-Type", "text/plain")
104
+	}
105
+	return req, nil
106
+}
107
+
108
+func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers http.Header) (*http.Response, error) {
109
+	req, err := cli.buildRequest(ctx, method, cli.getAPIPath(ctx, path, query), body, headers)
110
+	if err != nil {
111
+		return nil, err
112
+	}
113
+
114
+	resp, err := cli.doRequest(req)
115
+	switch {
116
+	case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
117
+		return nil, err
118
+	case err == nil:
119
+		return resp, cli.checkResponseErr(resp)
120
+	default:
121
+		return resp, err
122
+	}
123
+}
124
+
125
+func (cli *Client) doRequest(req *http.Request) (*http.Response, error) {
126
+	resp, err := cli.client.Do(req)
127
+	if err != nil {
128
+		if cli.scheme != "https" && strings.Contains(err.Error(), "malformed HTTP response") {
129
+			return nil, errConnectionFailed{fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)}
130
+		}
131
+
132
+		if cli.scheme == "https" && strings.Contains(err.Error(), "bad certificate") {
133
+			return nil, errConnectionFailed{errors.Wrap(err, "the server probably has client authentication (--tlsverify) enabled; check your TLS client certification settings")}
134
+		}
135
+
136
+		// Don't decorate context sentinel errors; users may be comparing to
137
+		// them directly.
138
+		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
139
+			return nil, err
140
+		}
141
+
142
+		var uErr *url.Error
143
+		if errors.As(err, &uErr) {
144
+			var nErr *net.OpError
145
+			if errors.As(uErr.Err, &nErr) {
146
+				if os.IsPermission(nErr.Err) {
147
+					return nil, errConnectionFailed{errors.Wrapf(err, "permission denied while trying to connect to the Docker daemon socket at %v", cli.host)}
148
+				}
149
+			}
150
+		}
151
+
152
+		var nErr net.Error
153
+		if errors.As(err, &nErr) {
154
+			// FIXME(thaJeztah): any net.Error should be considered a connection error (but we should include the original error)?
155
+			if nErr.Timeout() {
156
+				return nil, connectionFailed(cli.host)
157
+			}
158
+			if strings.Contains(nErr.Error(), "connection refused") || strings.Contains(nErr.Error(), "dial unix") {
159
+				return nil, connectionFailed(cli.host)
160
+			}
161
+		}
162
+
163
+		// Although there's not a strongly typed error for this in go-winio,
164
+		// lots of people are using the default configuration for the docker
165
+		// daemon on Windows where the daemon is listening on a named pipe
166
+		// `//./pipe/docker_engine, and the client must be running elevated.
167
+		// Give users a clue rather than the not-overly useful message
168
+		// such as `error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.26/info:
169
+		// open //./pipe/docker_engine: The system cannot find the file specified.`.
170
+		// Note we can't string compare "The system cannot find the file specified" as
171
+		// this is localised - for example in French the error would be
172
+		// `open //./pipe/docker_engine: Le fichier spécifié est introuvable.`
173
+		if strings.Contains(err.Error(), `open //./pipe/docker_engine`) {
174
+			// Checks if client is running with elevated privileges
175
+			if f, elevatedErr := os.Open(`\\.\PHYSICALDRIVE0`); elevatedErr != nil {
176
+				err = errors.Wrap(err, "in the default daemon configuration on Windows, the docker client must be run with elevated privileges to connect")
177
+			} else {
178
+				_ = f.Close()
179
+				err = errors.Wrap(err, "this error may indicate that the docker daemon is not running")
180
+			}
181
+		}
182
+
183
+		return nil, errConnectionFailed{errors.Wrap(err, "error during connect")}
184
+	}
185
+
186
+	return resp, nil
187
+}
188
+
189
+func (cli *Client) checkResponseErr(serverResp *http.Response) (retErr error) {
190
+	if serverResp == nil {
191
+		return nil
192
+	}
193
+	if serverResp.StatusCode >= http.StatusOK && serverResp.StatusCode < http.StatusBadRequest {
194
+		return nil
195
+	}
196
+	defer func() {
197
+		retErr = httpErrorFromStatusCode(retErr, serverResp.StatusCode)
198
+	}()
199
+
200
+	var body []byte
201
+	var err error
202
+	var reqURL string
203
+	if serverResp.Request != nil {
204
+		reqURL = serverResp.Request.URL.String()
205
+	}
206
+	statusMsg := serverResp.Status
207
+	if statusMsg == "" {
208
+		statusMsg = http.StatusText(serverResp.StatusCode)
209
+	}
210
+	if serverResp.Body != nil {
211
+		bodyMax := 1 * 1024 * 1024 // 1 MiB
212
+		bodyR := &io.LimitedReader{
213
+			R: serverResp.Body,
214
+			N: int64(bodyMax),
215
+		}
216
+		body, err = io.ReadAll(bodyR)
217
+		if err != nil {
218
+			return err
219
+		}
220
+		if bodyR.N == 0 {
221
+			if reqURL != "" {
222
+				return fmt.Errorf("request returned %s with a message (> %d bytes) for API route and version %s, check if the server supports the requested API version", statusMsg, bodyMax, reqURL)
223
+			}
224
+			return fmt.Errorf("request returned %s with a message (> %d bytes); check if the server supports the requested API version", statusMsg, bodyMax)
225
+		}
226
+	}
227
+	if len(body) == 0 {
228
+		if reqURL != "" {
229
+			return fmt.Errorf("request returned %s for API route and version %s, check if the server supports the requested API version", statusMsg, reqURL)
230
+		}
231
+		return fmt.Errorf("request returned %s; check if the server supports the requested API version", statusMsg)
232
+	}
233
+
234
+	var daemonErr error
235
+	if serverResp.Header.Get("Content-Type") == "application/json" {
236
+		var errorResponse types.ErrorResponse
237
+		if err := json.Unmarshal(body, &errorResponse); err != nil {
238
+			return errors.Wrap(err, "Error reading JSON")
239
+		}
240
+		if errorResponse.Message == "" {
241
+			// Error-message is empty, which means that we successfully parsed the
242
+			// JSON-response (no error produced), but it didn't contain an error
243
+			// message. This could either be because the response was empty, or
244
+			// the response was valid JSON, but not with the expected schema
245
+			// ([types.ErrorResponse]).
246
+			//
247
+			// We cannot use "strict" JSON handling (json.NewDecoder with DisallowUnknownFields)
248
+			// due to the API using an open schema (we must anticipate fields
249
+			// being added to [types.ErrorResponse] in the future, and not
250
+			// reject those responses.
251
+			//
252
+			// For these cases, we construct an error with the status-code
253
+			// returned, but we could consider returning (a truncated version
254
+			// of) the actual response as-is.
255
+			//
256
+			// TODO(thaJeztah): consider adding a log.Debug to allow clients to debug the actual response when enabling debug logging.
257
+			daemonErr = fmt.Errorf(`API returned a %d (%s) but provided no error-message`,
258
+				serverResp.StatusCode,
259
+				http.StatusText(serverResp.StatusCode),
260
+			)
261
+		} else {
262
+			daemonErr = errors.New(strings.TrimSpace(errorResponse.Message))
263
+		}
264
+	} else {
265
+		// Fall back to returning the response as-is for API versions < 1.24
266
+		// that didn't support JSON error responses, and for situations
267
+		// where a plain text error is returned. This branch may also catch
268
+		// situations where a proxy is involved, returning a HTML response.
269
+		daemonErr = errors.New(strings.TrimSpace(string(body)))
270
+	}
271
+	return errors.Wrap(daemonErr, "Error response from daemon")
272
+}
273
+
274
+func (cli *Client) addHeaders(req *http.Request, headers http.Header) *http.Request {
275
+	// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
276
+	// then the user can't change OUR headers
277
+	for k, v := range cli.customHTTPHeaders {
278
+		req.Header.Set(k, v)
279
+	}
280
+
281
+	for k, v := range headers {
282
+		req.Header[http.CanonicalHeaderKey(k)] = v
283
+	}
284
+
285
+	if cli.userAgent != nil {
286
+		if *cli.userAgent == "" {
287
+			req.Header.Del("User-Agent")
288
+		} else {
289
+			req.Header.Set("User-Agent", *cli.userAgent)
290
+		}
291
+	}
292
+	return req
293
+}
294
+
295
+func encodeData(data interface{}) (*bytes.Buffer, error) {
296
+	params := bytes.NewBuffer(nil)
297
+	if data != nil {
298
+		if err := json.NewEncoder(params).Encode(data); err != nil {
299
+			return nil, err
300
+		}
301
+	}
302
+	return params, nil
303
+}
304
+
305
+func ensureReaderClosed(response *http.Response) {
306
+	if response != nil && response.Body != nil {
307
+		// Drain up to 512 bytes and close the body to let the Transport reuse the connection
308
+		// see https://github.com/google/go-github/pull/317/files#r57536827
309
+		//
310
+		// TODO(thaJeztah): see if this optimization is still needed, or already implemented in stdlib,
311
+		//   and check if context-cancellation should handle this as well. If still needed, consider
312
+		//   wrapping response.Body, or returning a "closer()" from [Client.sendRequest] and related
313
+		//   methods.
314
+		_, _ = io.CopyN(io.Discard, response.Body, 512)
315
+		_ = response.Body.Close()
316
+	}
317
+}
0 318
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+
6
+	"github.com/moby/moby/api/types/swarm"
7
+)
8
+
9
+// SecretCreate creates a new secret.
10
+func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (swarm.SecretCreateResponse, error) {
11
+	if err := cli.NewVersionError(ctx, "1.25", "secret create"); err != nil {
12
+		return swarm.SecretCreateResponse{}, err
13
+	}
14
+	resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil)
15
+	defer ensureReaderClosed(resp)
16
+	if err != nil {
17
+		return swarm.SecretCreateResponse{}, err
18
+	}
19
+
20
+	var response swarm.SecretCreateResponse
21
+	err = json.NewDecoder(resp.Body).Decode(&response)
22
+	return response, err
23
+}
0 24
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"context"
5
+	"encoding/json"
6
+	"io"
7
+
8
+	"github.com/moby/moby/api/types/swarm"
9
+)
10
+
11
+// SecretInspectWithRaw returns the secret information with raw data
12
+func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.Secret, []byte, error) {
13
+	id, err := trimID("secret", id)
14
+	if err != nil {
15
+		return swarm.Secret{}, nil, err
16
+	}
17
+	if err := cli.NewVersionError(ctx, "1.25", "secret inspect"); err != nil {
18
+		return swarm.Secret{}, nil, err
19
+	}
20
+	resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
21
+	defer ensureReaderClosed(resp)
22
+	if err != nil {
23
+		return swarm.Secret{}, nil, err
24
+	}
25
+
26
+	body, err := io.ReadAll(resp.Body)
27
+	if err != nil {
28
+		return swarm.Secret{}, nil, err
29
+	}
30
+
31
+	var secret swarm.Secret
32
+	rdr := bytes.NewReader(body)
33
+	err = json.NewDecoder(rdr).Decode(&secret)
34
+
35
+	return secret, body, err
36
+}
0 37
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types/filters"
8
+	"github.com/moby/moby/api/types/swarm"
9
+)
10
+
11
+// SecretList returns the list of secrets.
12
+func (cli *Client) SecretList(ctx context.Context, options swarm.SecretListOptions) ([]swarm.Secret, error) {
13
+	if err := cli.NewVersionError(ctx, "1.25", "secret list"); err != nil {
14
+		return nil, err
15
+	}
16
+	query := url.Values{}
17
+
18
+	if options.Filters.Len() > 0 {
19
+		filterJSON, err := filters.ToJSON(options.Filters)
20
+		if err != nil {
21
+			return nil, err
22
+		}
23
+
24
+		query.Set("filters", filterJSON)
25
+	}
26
+
27
+	resp, err := cli.get(ctx, "/secrets", query, nil)
28
+	defer ensureReaderClosed(resp)
29
+	if err != nil {
30
+		return nil, err
31
+	}
32
+
33
+	var secrets []swarm.Secret
34
+	err = json.NewDecoder(resp.Body).Decode(&secrets)
35
+	return secrets, err
36
+}
0 37
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+package client
1
+
2
+import "context"
3
+
4
+// SecretRemove removes a secret.
5
+func (cli *Client) SecretRemove(ctx context.Context, id string) error {
6
+	id, err := trimID("secret", id)
7
+	if err != nil {
8
+		return err
9
+	}
10
+	if err := cli.NewVersionError(ctx, "1.25", "secret remove"); err != nil {
11
+		return err
12
+	}
13
+	resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
14
+	defer ensureReaderClosed(resp)
15
+	return err
16
+}
0 17
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+
6
+	"github.com/moby/moby/api/types/swarm"
7
+)
8
+
9
+// SecretUpdate attempts to update a secret.
10
+func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error {
11
+	id, err := trimID("secret", id)
12
+	if err != nil {
13
+		return err
14
+	}
15
+	if err := cli.NewVersionError(ctx, "1.25", "secret update"); err != nil {
16
+		return err
17
+	}
18
+	query := url.Values{}
19
+	query.Set("version", version.String())
20
+	resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, secret, nil)
21
+	ensureReaderClosed(resp)
22
+	return err
23
+}
0 24
new file mode 100644
... ...
@@ -0,0 +1,212 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"fmt"
6
+	"net/http"
7
+	"strings"
8
+
9
+	"github.com/distribution/reference"
10
+	"github.com/moby/moby/api/types/registry"
11
+	"github.com/moby/moby/api/types/swarm"
12
+	"github.com/moby/moby/api/types/versions"
13
+	"github.com/opencontainers/go-digest"
14
+	"github.com/pkg/errors"
15
+)
16
+
17
+// ServiceCreate creates a new service.
18
+func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options swarm.ServiceCreateOptions) (swarm.ServiceCreateResponse, error) {
19
+	var response swarm.ServiceCreateResponse
20
+
21
+	// Make sure we negotiated (if the client is configured to do so),
22
+	// as code below contains API-version specific handling of options.
23
+	//
24
+	// Normally, version-negotiation (if enabled) would not happen until
25
+	// the API request is made.
26
+	if err := cli.checkVersion(ctx); err != nil {
27
+		return response, err
28
+	}
29
+
30
+	// Make sure containerSpec is not nil when no runtime is set or the runtime is set to container
31
+	if service.TaskTemplate.ContainerSpec == nil && (service.TaskTemplate.Runtime == "" || service.TaskTemplate.Runtime == swarm.RuntimeContainer) {
32
+		service.TaskTemplate.ContainerSpec = &swarm.ContainerSpec{}
33
+	}
34
+
35
+	if err := validateServiceSpec(service); err != nil {
36
+		return response, err
37
+	}
38
+	if versions.LessThan(cli.version, "1.30") {
39
+		if err := validateAPIVersion(service, cli.version); err != nil {
40
+			return response, err
41
+		}
42
+	}
43
+
44
+	// ensure that the image is tagged
45
+	var resolveWarning string
46
+	switch {
47
+	case service.TaskTemplate.ContainerSpec != nil:
48
+		if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" {
49
+			service.TaskTemplate.ContainerSpec.Image = taggedImg
50
+		}
51
+		if options.QueryRegistry {
52
+			resolveWarning = resolveContainerSpecImage(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
53
+		}
54
+	case service.TaskTemplate.PluginSpec != nil:
55
+		if taggedImg := imageWithTagString(service.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
56
+			service.TaskTemplate.PluginSpec.Remote = taggedImg
57
+		}
58
+		if options.QueryRegistry {
59
+			resolveWarning = resolvePluginSpecRemote(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
60
+		}
61
+	}
62
+
63
+	headers := http.Header{}
64
+	if versions.LessThan(cli.version, "1.30") {
65
+		// the custom "version" header was used by engine API before 20.10
66
+		// (API 1.30) to switch between client- and server-side lookup of
67
+		// image digests.
68
+		headers["version"] = []string{cli.version}
69
+	}
70
+	if options.EncodedRegistryAuth != "" {
71
+		headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth}
72
+	}
73
+	resp, err := cli.post(ctx, "/services/create", nil, service, headers)
74
+	defer ensureReaderClosed(resp)
75
+	if err != nil {
76
+		return response, err
77
+	}
78
+
79
+	err = json.NewDecoder(resp.Body).Decode(&response)
80
+	if resolveWarning != "" {
81
+		response.Warnings = append(response.Warnings, resolveWarning)
82
+	}
83
+
84
+	return response, err
85
+}
86
+
87
+func resolveContainerSpecImage(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
88
+	var warning string
89
+	if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.ContainerSpec.Image, encodedAuth); err != nil {
90
+		warning = digestWarning(taskSpec.ContainerSpec.Image)
91
+	} else {
92
+		taskSpec.ContainerSpec.Image = img
93
+		if len(imgPlatforms) > 0 {
94
+			if taskSpec.Placement == nil {
95
+				taskSpec.Placement = &swarm.Placement{}
96
+			}
97
+			taskSpec.Placement.Platforms = imgPlatforms
98
+		}
99
+	}
100
+	return warning
101
+}
102
+
103
+func resolvePluginSpecRemote(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
104
+	var warning string
105
+	if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.PluginSpec.Remote, encodedAuth); err != nil {
106
+		warning = digestWarning(taskSpec.PluginSpec.Remote)
107
+	} else {
108
+		taskSpec.PluginSpec.Remote = img
109
+		if len(imgPlatforms) > 0 {
110
+			if taskSpec.Placement == nil {
111
+				taskSpec.Placement = &swarm.Placement{}
112
+			}
113
+			taskSpec.Placement.Platforms = imgPlatforms
114
+		}
115
+	}
116
+	return warning
117
+}
118
+
119
+func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) {
120
+	distributionInspect, err := cli.DistributionInspect(ctx, image, encodedAuth)
121
+	var platforms []swarm.Platform
122
+	if err != nil {
123
+		return "", nil, err
124
+	}
125
+
126
+	imageWithDigest := imageWithDigestString(image, distributionInspect.Descriptor.Digest)
127
+
128
+	if len(distributionInspect.Platforms) > 0 {
129
+		platforms = make([]swarm.Platform, 0, len(distributionInspect.Platforms))
130
+		for _, p := range distributionInspect.Platforms {
131
+			// clear architecture field for arm. This is a temporary patch to address
132
+			// https://github.com/docker/swarmkit/issues/2294. The issue is that while
133
+			// image manifests report "arm" as the architecture, the node reports
134
+			// something like "armv7l" (includes the variant), which causes arm images
135
+			// to stop working with swarm mode. This patch removes the architecture
136
+			// constraint for arm images to ensure tasks get scheduled.
137
+			arch := p.Architecture
138
+			if strings.ToLower(arch) == "arm" {
139
+				arch = ""
140
+			}
141
+			platforms = append(platforms, swarm.Platform{
142
+				Architecture: arch,
143
+				OS:           p.OS,
144
+			})
145
+		}
146
+	}
147
+	return imageWithDigest, platforms, err
148
+}
149
+
150
+// imageWithDigestString takes an image string and a digest, and updates
151
+// the image string if it didn't originally contain a digest. It returns
152
+// image unmodified in other situations.
153
+func imageWithDigestString(image string, dgst digest.Digest) string {
154
+	namedRef, err := reference.ParseNormalizedNamed(image)
155
+	if err == nil {
156
+		if _, isCanonical := namedRef.(reference.Canonical); !isCanonical {
157
+			// ensure that image gets a default tag if none is provided
158
+			img, err := reference.WithDigest(namedRef, dgst)
159
+			if err == nil {
160
+				return reference.FamiliarString(img)
161
+			}
162
+		}
163
+	}
164
+	return image
165
+}
166
+
167
+// imageWithTagString takes an image string, and returns a tagged image
168
+// string, adding a 'latest' tag if one was not provided. It returns an
169
+// empty string if a canonical reference was provided
170
+func imageWithTagString(image string) string {
171
+	namedRef, err := reference.ParseNormalizedNamed(image)
172
+	if err == nil {
173
+		return reference.FamiliarString(reference.TagNameOnly(namedRef))
174
+	}
175
+	return ""
176
+}
177
+
178
+// digestWarning constructs a formatted warning string using the
179
+// image name that could not be pinned by digest. The formatting
180
+// is hardcoded, but could me made smarter in the future
181
+func digestWarning(image string) string {
182
+	return fmt.Sprintf("image %s could not be accessed on a registry to record\nits digest. Each node will access %s independently,\npossibly leading to different nodes running different\nversions of the image.\n", image, image)
183
+}
184
+
185
+func validateServiceSpec(s swarm.ServiceSpec) error {
186
+	if s.TaskTemplate.ContainerSpec != nil && s.TaskTemplate.PluginSpec != nil {
187
+		return errors.New("must not specify both a container spec and a plugin spec in the task template")
188
+	}
189
+	if s.TaskTemplate.PluginSpec != nil && s.TaskTemplate.Runtime != swarm.RuntimePlugin {
190
+		return errors.New("mismatched runtime with plugin spec")
191
+	}
192
+	if s.TaskTemplate.ContainerSpec != nil && (s.TaskTemplate.Runtime != "" && s.TaskTemplate.Runtime != swarm.RuntimeContainer) {
193
+		return errors.New("mismatched runtime with container spec")
194
+	}
195
+	return nil
196
+}
197
+
198
+func validateAPIVersion(c swarm.ServiceSpec, apiVersion string) error {
199
+	for _, m := range c.TaskTemplate.ContainerSpec.Mounts {
200
+		if m.BindOptions != nil {
201
+			if m.BindOptions.NonRecursive && versions.LessThan(apiVersion, "1.40") {
202
+				return errors.Errorf("bind-recursive=disabled requires API v1.40 or later")
203
+			}
204
+			// ReadOnlyNonRecursive can be safely ignored when API < 1.44
205
+			if m.BindOptions.ReadOnlyForceRecursive && versions.LessThan(apiVersion, "1.44") {
206
+				return errors.Errorf("bind-recursive=readonly requires API v1.44 or later")
207
+			}
208
+		}
209
+	}
210
+	return nil
211
+}
0 212
new file mode 100644
... ...
@@ -0,0 +1,38 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"context"
5
+	"encoding/json"
6
+	"fmt"
7
+	"io"
8
+	"net/url"
9
+
10
+	"github.com/moby/moby/api/types/swarm"
11
+)
12
+
13
+// ServiceInspectWithRaw returns the service information and the raw data.
14
+func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string, opts swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
15
+	serviceID, err := trimID("service", serviceID)
16
+	if err != nil {
17
+		return swarm.Service{}, nil, err
18
+	}
19
+
20
+	query := url.Values{}
21
+	query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults))
22
+	resp, err := cli.get(ctx, "/services/"+serviceID, query, nil)
23
+	defer ensureReaderClosed(resp)
24
+	if err != nil {
25
+		return swarm.Service{}, nil, err
26
+	}
27
+
28
+	body, err := io.ReadAll(resp.Body)
29
+	if err != nil {
30
+		return swarm.Service{}, nil, err
31
+	}
32
+
33
+	var response swarm.Service
34
+	rdr := bytes.NewReader(body)
35
+	err = json.NewDecoder(rdr).Decode(&response)
36
+	return response, body, err
37
+}
0 38
new file mode 100644
... ...
@@ -0,0 +1,38 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types/filters"
8
+	"github.com/moby/moby/api/types/swarm"
9
+)
10
+
11
+// ServiceList returns the list of services.
12
+func (cli *Client) ServiceList(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
13
+	query := url.Values{}
14
+
15
+	if options.Filters.Len() > 0 {
16
+		filterJSON, err := filters.ToJSON(options.Filters)
17
+		if err != nil {
18
+			return nil, err
19
+		}
20
+
21
+		query.Set("filters", filterJSON)
22
+	}
23
+
24
+	if options.Status {
25
+		query.Set("status", "true")
26
+	}
27
+
28
+	resp, err := cli.get(ctx, "/services", query, nil)
29
+	defer ensureReaderClosed(resp)
30
+	if err != nil {
31
+		return nil, err
32
+	}
33
+
34
+	var services []swarm.Service
35
+	err = json.NewDecoder(resp.Body).Decode(&services)
36
+	return services, err
37
+}
0 38
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"net/url"
6
+	"time"
7
+
8
+	"github.com/moby/moby/api/types/container"
9
+	timetypes "github.com/moby/moby/api/types/time"
10
+	"github.com/pkg/errors"
11
+)
12
+
13
+// ServiceLogs returns the logs generated by a service in an io.ReadCloser.
14
+// It's up to the caller to close the stream.
15
+func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options container.LogsOptions) (io.ReadCloser, error) {
16
+	serviceID, err := trimID("service", serviceID)
17
+	if err != nil {
18
+		return nil, err
19
+	}
20
+
21
+	query := url.Values{}
22
+	if options.ShowStdout {
23
+		query.Set("stdout", "1")
24
+	}
25
+
26
+	if options.ShowStderr {
27
+		query.Set("stderr", "1")
28
+	}
29
+
30
+	if options.Since != "" {
31
+		ts, err := timetypes.GetTimestamp(options.Since, time.Now())
32
+		if err != nil {
33
+			return nil, errors.Wrap(err, `invalid value for "since"`)
34
+		}
35
+		query.Set("since", ts)
36
+	}
37
+
38
+	if options.Timestamps {
39
+		query.Set("timestamps", "1")
40
+	}
41
+
42
+	if options.Details {
43
+		query.Set("details", "1")
44
+	}
45
+
46
+	if options.Follow {
47
+		query.Set("follow", "1")
48
+	}
49
+	query.Set("tail", options.Tail)
50
+
51
+	resp, err := cli.get(ctx, "/services/"+serviceID+"/logs", query, nil)
52
+	if err != nil {
53
+		return nil, err
54
+	}
55
+	return resp.Body, nil
56
+}
0 57
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+package client
1
+
2
+import "context"
3
+
4
+// ServiceRemove kills and removes a service.
5
+func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error {
6
+	serviceID, err := trimID("service", serviceID)
7
+	if err != nil {
8
+		return err
9
+	}
10
+
11
+	resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil)
12
+	defer ensureReaderClosed(resp)
13
+	return err
14
+}
0 15
new file mode 100644
... ...
@@ -0,0 +1,89 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/http"
6
+	"net/url"
7
+
8
+	"github.com/moby/moby/api/types/registry"
9
+	"github.com/moby/moby/api/types/swarm"
10
+	"github.com/moby/moby/api/types/versions"
11
+)
12
+
13
+// ServiceUpdate updates a Service. The version number is required to avoid conflicting writes.
14
+// It should be the value as set *before* the update. You can find this value in the Meta field
15
+// of swarm.Service, which can be found using ServiceInspectWithRaw.
16
+func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
17
+	serviceID, err := trimID("service", serviceID)
18
+	if err != nil {
19
+		return swarm.ServiceUpdateResponse{}, err
20
+	}
21
+
22
+	// Make sure we negotiated (if the client is configured to do so),
23
+	// as code below contains API-version specific handling of options.
24
+	//
25
+	// Normally, version-negotiation (if enabled) would not happen until
26
+	// the API request is made.
27
+	if err := cli.checkVersion(ctx); err != nil {
28
+		return swarm.ServiceUpdateResponse{}, err
29
+	}
30
+
31
+	query := url.Values{}
32
+	if options.RegistryAuthFrom != "" {
33
+		query.Set("registryAuthFrom", options.RegistryAuthFrom)
34
+	}
35
+
36
+	if options.Rollback != "" {
37
+		query.Set("rollback", options.Rollback)
38
+	}
39
+
40
+	query.Set("version", version.String())
41
+
42
+	if err := validateServiceSpec(service); err != nil {
43
+		return swarm.ServiceUpdateResponse{}, err
44
+	}
45
+
46
+	// ensure that the image is tagged
47
+	var resolveWarning string
48
+	switch {
49
+	case service.TaskTemplate.ContainerSpec != nil:
50
+		if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" {
51
+			service.TaskTemplate.ContainerSpec.Image = taggedImg
52
+		}
53
+		if options.QueryRegistry {
54
+			resolveWarning = resolveContainerSpecImage(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
55
+		}
56
+	case service.TaskTemplate.PluginSpec != nil:
57
+		if taggedImg := imageWithTagString(service.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
58
+			service.TaskTemplate.PluginSpec.Remote = taggedImg
59
+		}
60
+		if options.QueryRegistry {
61
+			resolveWarning = resolvePluginSpecRemote(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
62
+		}
63
+	}
64
+
65
+	headers := http.Header{}
66
+	if versions.LessThan(cli.version, "1.30") {
67
+		// the custom "version" header was used by engine API before 20.10
68
+		// (API 1.30) to switch between client- and server-side lookup of
69
+		// image digests.
70
+		headers["version"] = []string{cli.version}
71
+	}
72
+	if options.EncodedRegistryAuth != "" {
73
+		headers.Set(registry.AuthHeader, options.EncodedRegistryAuth)
74
+	}
75
+	resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers)
76
+	defer ensureReaderClosed(resp)
77
+	if err != nil {
78
+		return swarm.ServiceUpdateResponse{}, err
79
+	}
80
+
81
+	var response swarm.ServiceUpdateResponse
82
+	err = json.NewDecoder(resp.Body).Decode(&response)
83
+	if resolveWarning != "" {
84
+		response.Warnings = append(response.Warnings, resolveWarning)
85
+	}
86
+
87
+	return response, err
88
+}
0 89
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+
6
+	"github.com/moby/moby/api/types/swarm"
7
+)
8
+
9
+// SwarmGetUnlockKey retrieves the swarm's unlock key.
10
+func (cli *Client) SwarmGetUnlockKey(ctx context.Context) (swarm.UnlockKeyResponse, error) {
11
+	resp, err := cli.get(ctx, "/swarm/unlockkey", nil, nil)
12
+	defer ensureReaderClosed(resp)
13
+	if err != nil {
14
+		return swarm.UnlockKeyResponse{}, err
15
+	}
16
+
17
+	var response swarm.UnlockKeyResponse
18
+	err = json.NewDecoder(resp.Body).Decode(&response)
19
+	return response, err
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+
6
+	"github.com/moby/moby/api/types/swarm"
7
+)
8
+
9
+// SwarmInit initializes the swarm.
10
+func (cli *Client) SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) {
11
+	resp, err := cli.post(ctx, "/swarm/init", nil, req, nil)
12
+	defer ensureReaderClosed(resp)
13
+	if err != nil {
14
+		return "", err
15
+	}
16
+
17
+	var response string
18
+	err = json.NewDecoder(resp.Body).Decode(&response)
19
+	return response, err
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+
6
+	"github.com/moby/moby/api/types/swarm"
7
+)
8
+
9
+// SwarmInspect inspects the swarm.
10
+func (cli *Client) SwarmInspect(ctx context.Context) (swarm.Swarm, error) {
11
+	resp, err := cli.get(ctx, "/swarm", nil, nil)
12
+	defer ensureReaderClosed(resp)
13
+	if err != nil {
14
+		return swarm.Swarm{}, err
15
+	}
16
+
17
+	var response swarm.Swarm
18
+	err = json.NewDecoder(resp.Body).Decode(&response)
19
+	return response, err
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/moby/moby/api/types/swarm"
6
+)
7
+
8
+// SwarmJoin joins the swarm.
9
+func (cli *Client) SwarmJoin(ctx context.Context, req swarm.JoinRequest) error {
10
+	resp, err := cli.post(ctx, "/swarm/join", nil, req, nil)
11
+	ensureReaderClosed(resp)
12
+	return err
13
+}
0 14
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+)
6
+
7
+// SwarmLeave leaves the swarm.
8
+func (cli *Client) SwarmLeave(ctx context.Context, force bool) error {
9
+	query := url.Values{}
10
+	if force {
11
+		query.Set("force", "1")
12
+	}
13
+	resp, err := cli.post(ctx, "/swarm/leave", query, nil, nil)
14
+	ensureReaderClosed(resp)
15
+	return err
16
+}
0 17
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/moby/moby/api/types/swarm"
6
+)
7
+
8
+// SwarmUnlock unlocks locked swarm.
9
+func (cli *Client) SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error {
10
+	resp, err := cli.post(ctx, "/swarm/unlock", nil, req, nil)
11
+	ensureReaderClosed(resp)
12
+	return err
13
+}
0 14
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+	"strconv"
6
+
7
+	"github.com/moby/moby/api/types/swarm"
8
+)
9
+
10
+// SwarmUpdate updates the swarm.
11
+func (cli *Client) SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags swarm.UpdateFlags) error {
12
+	query := url.Values{}
13
+	query.Set("version", version.String())
14
+	query.Set("rotateWorkerToken", strconv.FormatBool(flags.RotateWorkerToken))
15
+	query.Set("rotateManagerToken", strconv.FormatBool(flags.RotateManagerToken))
16
+	query.Set("rotateManagerUnlockKey", strconv.FormatBool(flags.RotateManagerUnlockKey))
17
+	resp, err := cli.post(ctx, "/swarm/update", query, swarm, nil)
18
+	ensureReaderClosed(resp)
19
+	return err
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,34 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"context"
5
+	"encoding/json"
6
+	"io"
7
+
8
+	"github.com/moby/moby/api/types/swarm"
9
+)
10
+
11
+// TaskInspectWithRaw returns the task information and its raw representation.
12
+func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) {
13
+	taskID, err := trimID("task", taskID)
14
+	if err != nil {
15
+		return swarm.Task{}, nil, err
16
+	}
17
+
18
+	resp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
19
+	defer ensureReaderClosed(resp)
20
+	if err != nil {
21
+		return swarm.Task{}, nil, err
22
+	}
23
+
24
+	body, err := io.ReadAll(resp.Body)
25
+	if err != nil {
26
+		return swarm.Task{}, nil, err
27
+	}
28
+
29
+	var response swarm.Task
30
+	rdr := bytes.NewReader(body)
31
+	err = json.NewDecoder(rdr).Decode(&response)
32
+	return response, body, err
33
+}
0 34
new file mode 100644
... ...
@@ -0,0 +1,34 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types/filters"
8
+	"github.com/moby/moby/api/types/swarm"
9
+)
10
+
11
+// TaskList returns the list of tasks.
12
+func (cli *Client) TaskList(ctx context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
13
+	query := url.Values{}
14
+
15
+	if options.Filters.Len() > 0 {
16
+		filterJSON, err := filters.ToJSON(options.Filters)
17
+		if err != nil {
18
+			return nil, err
19
+		}
20
+
21
+		query.Set("filters", filterJSON)
22
+	}
23
+
24
+	resp, err := cli.get(ctx, "/tasks", query, nil)
25
+	defer ensureReaderClosed(resp)
26
+	if err != nil {
27
+		return nil, err
28
+	}
29
+
30
+	var tasks []swarm.Task
31
+	err = json.NewDecoder(resp.Body).Decode(&tasks)
32
+	return tasks, err
33
+}
0 34
new file mode 100644
... ...
@@ -0,0 +1,51 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"net/url"
6
+	"time"
7
+
8
+	"github.com/moby/moby/api/types/container"
9
+	timetypes "github.com/moby/moby/api/types/time"
10
+)
11
+
12
+// TaskLogs returns the logs generated by a task in an io.ReadCloser.
13
+// It's up to the caller to close the stream.
14
+func (cli *Client) TaskLogs(ctx context.Context, taskID string, options container.LogsOptions) (io.ReadCloser, error) {
15
+	query := url.Values{}
16
+	if options.ShowStdout {
17
+		query.Set("stdout", "1")
18
+	}
19
+
20
+	if options.ShowStderr {
21
+		query.Set("stderr", "1")
22
+	}
23
+
24
+	if options.Since != "" {
25
+		ts, err := timetypes.GetTimestamp(options.Since, time.Now())
26
+		if err != nil {
27
+			return nil, err
28
+		}
29
+		query.Set("since", ts)
30
+	}
31
+
32
+	if options.Timestamps {
33
+		query.Set("timestamps", "1")
34
+	}
35
+
36
+	if options.Details {
37
+		query.Set("details", "1")
38
+	}
39
+
40
+	if options.Follow {
41
+		query.Set("follow", "1")
42
+	}
43
+	query.Set("tail", options.Tail)
44
+
45
+	resp, err := cli.get(ctx, "/tasks/"+taskID+"/logs", query, nil)
46
+	if err != nil {
47
+		return nil, err
48
+	}
49
+	return resp.Body, nil
50
+}
0 51
new file mode 100644
... ...
@@ -0,0 +1,83 @@
0
+package client
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"net/url"
6
+	"strings"
7
+
8
+	cerrdefs "github.com/containerd/errdefs"
9
+	"github.com/moby/moby/api/types/filters"
10
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
11
+)
12
+
13
+type emptyIDError string
14
+
15
+func (e emptyIDError) InvalidParameter() {}
16
+
17
+func (e emptyIDError) Error() string {
18
+	return "invalid " + string(e) + " name or ID: value is empty"
19
+}
20
+
21
+// trimID trims the given object-ID / name, returning an error if it's empty.
22
+func trimID(objType, id string) (string, error) {
23
+	id = strings.TrimSpace(id)
24
+	if id == "" {
25
+		return "", emptyIDError(objType)
26
+	}
27
+	return id, nil
28
+}
29
+
30
+// getFiltersQuery returns a url query with "filters" query term, based on the
31
+// filters provided.
32
+func getFiltersQuery(f filters.Args) (url.Values, error) {
33
+	query := url.Values{}
34
+	if f.Len() > 0 {
35
+		filterJSON, err := filters.ToJSON(f)
36
+		if err != nil {
37
+			return query, err
38
+		}
39
+		query.Set("filters", filterJSON)
40
+	}
41
+	return query, nil
42
+}
43
+
44
+// encodePlatforms marshals the given platform(s) to JSON format, to
45
+// be used for query-parameters for filtering / selecting platforms.
46
+func encodePlatforms(platform ...ocispec.Platform) ([]string, error) {
47
+	if len(platform) == 0 {
48
+		return []string{}, nil
49
+	}
50
+	if len(platform) == 1 {
51
+		p, err := encodePlatform(&platform[0])
52
+		if err != nil {
53
+			return nil, err
54
+		}
55
+		return []string{p}, nil
56
+	}
57
+
58
+	seen := make(map[string]struct{}, len(platform))
59
+	out := make([]string, 0, len(platform))
60
+	for i := range platform {
61
+		p, err := encodePlatform(&platform[i])
62
+		if err != nil {
63
+			return nil, err
64
+		}
65
+		if _, ok := seen[p]; !ok {
66
+			out = append(out, p)
67
+			seen[p] = struct{}{}
68
+		}
69
+	}
70
+	return out, nil
71
+}
72
+
73
+// encodePlatform marshals the given platform to JSON format, to
74
+// be used for query-parameters for filtering / selecting platforms. It
75
+// is used as a helper for encodePlatforms,
76
+func encodePlatform(platform *ocispec.Platform) (string, error) {
77
+	p, err := json.Marshal(platform)
78
+	if err != nil {
79
+		return "", fmt.Errorf("%w: invalid platform: %v", cerrdefs.ErrInvalidArgument, err)
80
+	}
81
+	return string(p), nil
82
+}
0 83
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+
6
+	"github.com/moby/moby/api/types"
7
+)
8
+
9
+// ServerVersion returns information of the docker client and server host.
10
+func (cli *Client) ServerVersion(ctx context.Context) (types.Version, error) {
11
+	resp, err := cli.get(ctx, "/version", nil, nil)
12
+	defer ensureReaderClosed(resp)
13
+	if err != nil {
14
+		return types.Version{}, err
15
+	}
16
+
17
+	var server types.Version
18
+	err = json.NewDecoder(resp.Body).Decode(&server)
19
+	return server, err
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+
6
+	"github.com/moby/moby/api/types/volume"
7
+)
8
+
9
+// VolumeCreate creates a volume in the docker host.
10
+func (cli *Client) VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error) {
11
+	resp, err := cli.post(ctx, "/volumes/create", nil, options, nil)
12
+	defer ensureReaderClosed(resp)
13
+	if err != nil {
14
+		return volume.Volume{}, err
15
+	}
16
+
17
+	var vol volume.Volume
18
+	err = json.NewDecoder(resp.Body).Decode(&vol)
19
+	return vol, err
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,40 @@
0
+package client
1
+
2
+import (
3
+	"bytes"
4
+	"context"
5
+	"encoding/json"
6
+	"io"
7
+
8
+	"github.com/moby/moby/api/types/volume"
9
+)
10
+
11
+// VolumeInspect returns the information about a specific volume in the docker host.
12
+func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (volume.Volume, error) {
13
+	vol, _, err := cli.VolumeInspectWithRaw(ctx, volumeID)
14
+	return vol, err
15
+}
16
+
17
+// VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation
18
+func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error) {
19
+	volumeID, err := trimID("volume", volumeID)
20
+	if err != nil {
21
+		return volume.Volume{}, nil, err
22
+	}
23
+
24
+	resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil)
25
+	defer ensureReaderClosed(resp)
26
+	if err != nil {
27
+		return volume.Volume{}, nil, err
28
+	}
29
+
30
+	body, err := io.ReadAll(resp.Body)
31
+	if err != nil {
32
+		return volume.Volume{}, nil, err
33
+	}
34
+
35
+	var vol volume.Volume
36
+	rdr := bytes.NewReader(body)
37
+	err = json.NewDecoder(rdr).Decode(&vol)
38
+	return vol, body, err
39
+}
0 40
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"net/url"
6
+
7
+	"github.com/moby/moby/api/types/filters"
8
+	"github.com/moby/moby/api/types/volume"
9
+)
10
+
11
+// VolumeList returns the volumes configured in the docker host.
12
+func (cli *Client) VolumeList(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error) {
13
+	query := url.Values{}
14
+
15
+	if options.Filters.Len() > 0 {
16
+		//nolint:staticcheck // ignore SA1019 for old code
17
+		filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
18
+		if err != nil {
19
+			return volume.ListResponse{}, err
20
+		}
21
+		query.Set("filters", filterJSON)
22
+	}
23
+	resp, err := cli.get(ctx, "/volumes", query, nil)
24
+	defer ensureReaderClosed(resp)
25
+	if err != nil {
26
+		return volume.ListResponse{}, err
27
+	}
28
+
29
+	var volumes volume.ListResponse
30
+	err = json.NewDecoder(resp.Body).Decode(&volumes)
31
+	return volumes, err
32
+}
0 33
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"encoding/json"
5
+	"fmt"
6
+
7
+	"github.com/moby/moby/api/types/filters"
8
+	"github.com/moby/moby/api/types/volume"
9
+)
10
+
11
+// VolumesPrune requests the daemon to delete unused data
12
+func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (volume.PruneReport, error) {
13
+	if err := cli.NewVersionError(ctx, "1.25", "volume prune"); err != nil {
14
+		return volume.PruneReport{}, err
15
+	}
16
+
17
+	query, err := getFiltersQuery(pruneFilters)
18
+	if err != nil {
19
+		return volume.PruneReport{}, err
20
+	}
21
+
22
+	resp, err := cli.post(ctx, "/volumes/prune", query, nil, nil)
23
+	defer ensureReaderClosed(resp)
24
+	if err != nil {
25
+		return volume.PruneReport{}, err
26
+	}
27
+
28
+	var report volume.PruneReport
29
+	if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
30
+		return volume.PruneReport{}, fmt.Errorf("Error retrieving volume prune report: %v", err)
31
+	}
32
+
33
+	return report, nil
34
+}
0 35
new file mode 100644
... ...
@@ -0,0 +1,34 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+
6
+	"github.com/moby/moby/api/types/versions"
7
+)
8
+
9
+// VolumeRemove removes a volume from the docker host.
10
+func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
11
+	volumeID, err := trimID("volume", volumeID)
12
+	if err != nil {
13
+		return err
14
+	}
15
+
16
+	query := url.Values{}
17
+	if force {
18
+		// Make sure we negotiated (if the client is configured to do so),
19
+		// as code below contains API-version specific handling of options.
20
+		//
21
+		// Normally, version-negotiation (if enabled) would not happen until
22
+		// the API request is made.
23
+		if err := cli.checkVersion(ctx); err != nil {
24
+			return err
25
+		}
26
+		if versions.GreaterThanOrEqualTo(cli.version, "1.25") {
27
+			query.Set("force", "1")
28
+		}
29
+	}
30
+	resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil)
31
+	defer ensureReaderClosed(resp)
32
+	return err
33
+}
0 34
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+package client
1
+
2
+import (
3
+	"context"
4
+	"net/url"
5
+
6
+	"github.com/moby/moby/api/types/swarm"
7
+	"github.com/moby/moby/api/types/volume"
8
+)
9
+
10
+// VolumeUpdate updates a volume. This only works for Cluster Volumes, and
11
+// only some fields can be updated.
12
+func (cli *Client) VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error {
13
+	volumeID, err := trimID("volume", volumeID)
14
+	if err != nil {
15
+		return err
16
+	}
17
+	if err := cli.NewVersionError(ctx, "1.42", "volume update"); err != nil {
18
+		return err
19
+	}
20
+
21
+	query := url.Values{}
22
+	query.Set("version", version.String())
23
+
24
+	resp, err := cli.put(ctx, "/volumes/"+volumeID, query, options, nil)
25
+	ensureReaderClosed(resp)
26
+	return err
27
+}
... ...
@@ -962,6 +962,9 @@ github.com/moby/moby/api/types/system
962 962
 github.com/moby/moby/api/types/time
963 963
 github.com/moby/moby/api/types/versions
964 964
 github.com/moby/moby/api/types/volume
965
+# github.com/moby/moby/client v0.0.0 => ./client
966
+## explicit; go 1.23.0
967
+github.com/moby/moby/client
965 968
 # github.com/moby/patternmatcher v0.6.0
966 969
 ## explicit; go 1.19
967 970
 github.com/moby/patternmatcher
... ...
@@ -1749,3 +1752,4 @@ tags.cncf.io/container-device-interface/pkg/parser
1749 1749
 ## explicit; go 1.19
1750 1750
 tags.cncf.io/container-device-interface/specs-go
1751 1751
 # github.com/moby/moby/api => ./api
1752
+# github.com/moby/moby/client => ./client