Compare commits

...

275 Commits

Author SHA1 Message Date
Paweł Gronowski
08440b6ee8 Merge pull request #51830 from thaJeztah/29.x_backport_windows-network-none
[docker-29.x backport] daemon/libnetwork: Fix panic in findHNSEp when IP networks are nil
2026-01-08 16:57:22 +00:00
Paweł Gronowski
b0e62060b0 Merge pull request #51829 from thaJeztah/29.x_backport_fix-image-mount
[docker-29.x backport] daemon/volumes: More fs friendly image mount layer names
2026-01-08 16:57:12 +00:00
Sebastiaan van Stijn
515dbc8c71 Merge pull request #51826 from thaJeztah/29.x_backport_45939-init-layer-cleanup
[docker-29.x backport] layer: Clean up init layer if initialization fails
2026-01-08 17:24:06 +01:00
Sebastiaan van Stijn
adf3073cb6 Merge pull request #51825 from thaJeztah/29.x_backport_archive_rm_deprecated
[docker-29.x backport] remove uses of deprecated go-archive consts
2026-01-08 17:23:43 +01:00
Sebastiaan van Stijn
8b2c317218 Merge pull request #51824 from thaJeztah/29.x_backport_45939-rw-layer-cleanup
[docker-29.x backport] layer: Clean up RW layer if mount metadata save fails
2026-01-08 17:23:09 +01:00
Paweł Gronowski
3eca177282 daemon/libnetwork: Fix panic in findHNSEp when IP networks are nil
Can happen for `docker run --network none ...`

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit fadd8dc47c)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-08 16:59:22 +01:00
Paweł Gronowski
c4f4c6765e daemon/volumes: More fs friendly image mount layer names
Hash the container ID, mount source and destination together to form a
layer name.

This ensures the generated names are filesystem-friendly and don't
exceed path length limits while maintaining uniqueness across different
mount points and containers.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit cb88c6ba10)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-08 16:56:18 +01:00
Sebastiaan van Stijn
f942bce11a Merge pull request #51821 from vvoland/51740-docker-29.x
[docker-29.x backport] vendor: github.com/moby/buildkit v0.26.3
2026-01-08 16:41:14 +01:00
Paweł Gronowski
a1f7fff7a9 daemon/layer_store: Use named return error for defer
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 26bb1af7e6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-08 11:00:00 +01:00
Jan Scheffler
0e600c7fc4 layer: Clean up init layer if initialization fails
Add cleanup for the init layer directory if any operation fails after
driver.CreateReadWrite() succeeds in initMount(). Previously, failures
in driver.Get(), initFunc(), or driver.Put() would leave an orphaned
overlay2 directory.

Related to moby/moby#45939

Signed-off-by: Jan Scheffler <jan.scheffler@qodev.ai>
(cherry picked from commit 3fdde529e7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-08 11:00:00 +01:00
Sebastiaan van Stijn
734bb626e4 remove uses of deprecated go-archive consts
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 7239c72eca)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-08 10:54:50 +01:00
Jan Scheffler
5eaae6db52 layer: Clean up RW layer if mount metadata save fails
Add cleanup for the RW layer directory if saveMount() fails after
driver.CreateReadWrite() succeeds. Previously, this failure path would
leave an orphaned overlay2 directory with no corresponding metadata.

Related to moby/moby#45939

Signed-off-by: Jan Scheffler <jan.scheffler@qodev.ai>
(cherry picked from commit d7a6250b91)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-08 10:49:11 +01:00
Jonathan A. Sternberg
8ebb104e36 vendor: github.com/moby/buildkit v0.26.3
Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
(cherry picked from commit c63bf203bf)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-01-07 16:44:38 +01:00
Paweł Gronowski
fbf3ed25f8 Merge pull request #51703 from vvoland/51700-docker-29.x
[docker-29.x backport] layer: Fix orphan creation in registerWithDescriptor
2025-12-12 13:08:08 +00:00
Jan Scheffler
518779c90b layer: Fix orphan creation in registerWithDescriptor
Start the metadata transaction before creating the overlay2 directory.
This ensures that if driver.Create() fails, we can properly cancel the
transaction. Previously, if StartTransaction() failed after driver.Create()
succeeded, the defer cleanup would not run (not registered yet), leaving
an orphaned overlay2 directory.

The fix reorders operations so that:
1. Transaction is started first (no filesystem changes yet)
2. Overlay2 directory is created second (transaction ready for cleanup)
3. Defer is registered after both succeed (tx is guaranteed non-nil)

If driver.Create() fails, the transaction is explicitly cancelled before
returning. The nil check for tx in the defer is no longer needed since
tx is guaranteed to exist when the defer runs.

Related to moby/moby#45939

Signed-off-by: Jan Scheffler <jan.scheffler@qodev.ai>
(cherry picked from commit 70004549fb)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-12 12:45:16 +01:00
Sebastiaan van Stijn
ecf8643fe1 Merge pull request #51699 from vvoland/51697-docker-29.x
[docker-29.x backport] vendor: github.com/containernetworking/plugins v1.9.0
2025-12-12 11:38:17 +01:00
Sebastiaan van Stijn
5a99e1d1a4 vendor: github.com/containernetworking/plugins v1.9.0
no changes in vendored code

includes a fix for CVE-2025-67499

full diff: https://github.com/containernetworking/plugins/compare/v1.8.0...v1.9.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 24bac4495e)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-12 10:39:19 +01:00
Sebastiaan van Stijn
bae170eeb7 Merge pull request #51695 from vvoland/51684-docker-29.x
[docker-29.x backport] daemon: disallow container port 0
2025-12-11 23:11:07 +01:00
Sebastiaan van Stijn
8f33623c5d Merge pull request #51691 from vvoland/51683-docker-29.x
[docker-29.x backport] daemon: buildCreateEndpointOptions: fix panic with "publish all"
2025-12-11 23:10:00 +01:00
Paweł Gronowski
bdc1e7b0fe Merge pull request #51693 from vvoland/51692-docker-29.x
[docker-29.x backport] daemon: clean up dead containers on start
2025-12-11 20:50:59 +00:00
Albin Kerouanton
298e2f7d52 daemon: disallow container port 0
Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
(cherry picked from commit 43780fe40c)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-11 21:42:30 +01:00
Albin Kerouanton
3376758770 daemon: clean up dead containers on start
Stopping the Engine while a container with autoremove set is running may
leave behind dead containers on disk. These containers aren't reclaimed
on next start, appear as "dead" in `docker ps -a` and can't be
inspected or removed by the user.

This bug has existed since a long time but became user visible with
9f5f4f5a42. Prior to that commit,
containers with no rwlayer weren't added to the in-memory viewdb, so
they weren't visible in `docker ps -a`. However, some dangling files
would still live on disk (e.g. folder in /var/lib/docker/containers,
mount points, etc).

The underlying issue is that when the daemon stops, it tries to stop all
running containers and then closes the containerd client. This leaves a
small window of time where the Engine might receive 'task stop' events
from containerd, and trigger autoremove. If the containerd client is
closed in parallel, the Engine is unable to complete the removal,
leaving the container in 'dead' state. In such case, the Engine logs the
following error:

    cannot remove container "bcbc98b4f5c2b072eb3c4ca673fa1c222d2a8af00bf58eae0f37085b9724ea46": Canceled: grpc: the client connection is closing: context canceled

Solving the underlying issue would require complex changes to the
shutdown sequence. Moreover, the same issue could also happen if the
daemon crashes while it deletes a container. Thus, add a cleanup step
on daemon startup to remove these dead containers.

Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
(cherry picked from commit ec9315cd4f)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-11 20:55:04 +01:00
Sebastiaan van Stijn
bb2e099c3a daemon/container: Container.BackfillEmptyPBs: prevent nil map
Make sure PortBindings is not a nil-map to match the behavior
we have when creating a container;
c64b781df2/daemon/internal/runconfig/config.go (L30-L47)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 2a191665b8)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-11 16:40:38 +01:00
Sebastiaan van Stijn
5898ee60f4 daemon: buildCreateEndpointOptions: fix panic with "publish all"
This code was added in 85b260fba8, but didn't
account for maps.Clone returning a `nil` map if the map cloned was `nil`.

This could lead to a panic, similar to the panic that was fixed in
7517464283d29969c4d3615397b369abd99ce395;

    panic: assignment to entry in nil map

    goroutine 498 [running]:

    github.com/moby/moby/v2/daemon.buildPortsRelatedCreateEndpointOptions(0x400042f348, 0xaaaabcc8f458?, 0x40006feb40)
        /root/build-deb/engine/daemon/network.go:1047 +0x844
    github.com/moby/moby/v2/daemon.buildCreateEndpointOptions(0x400042f348, 0x4001015040, 0x400027d320, 0x40006feb40, {0x0, 0x0, 0x4001506cb8?})
        /root/build-deb/engine/daemon/network.go:988 +0x20c
    github.com/moby/moby/v2/daemon.(*Daemon).connectToNetwork(0x4000898008, {0xaaaabe21d9f8, 0x4000f12b10}, 0x400089a008, 0x400042f348, {0x400077a9f0, 0x6}, 0x400027d320)
        /root/build-deb/engine/daemon/container_operations.go:738 +0x66c
    github.com/moby/moby/v2/daemon.(*Daemon).allocateNetwork(0x4000898008, {0xaaaabe21d9f8, 0x4000f12b10}, 0x400089a008, 0x400042f348)
        /root/build-deb/engine/daemon/container_operations.go:421 +0x298
    github.com/moby/moby/v2/daemon.(*Daemon).initializeCreatedTask(0x4000898008, {0xaaaabe21d9f8, 0x4000f12b10}, 0x400089a008, {0xaaaabe23dc60, 0x4000eb21c8}, 0x400042f348, 0xaaaabd4db3df?)
        /root/build-deb/engine/daemon/start_linux.go:37 +0x260
    github.com/moby/moby/v2/daemon.(*Daemon).containerStart(0x4000898008, {0xaaaabe21d9c0, 0xaaaabfa05300}, 0x400089a008, 0x400042f348, {0x0, 0x0}, {0x0, 0x0}, 0x1)
        /root/build-deb/engine/daemon/start.go:242 +0xba8
    github.com/moby/moby/v2/daemon.(*Daemon).restore.func4(0x400042f348, 0x400117f1f0)
        /root/build-deb/engine/daemon/daemon.go:633 +0x308
    created by github.com/moby/moby/v2/daemon.(*Daemon).restore in goroutine 1
        /root/build-deb/engine/daemon/daemon.go:607 +0x5ec

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 695010ba2e)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-11 16:40:38 +01:00
Paweł Gronowski
05a5be917d Merge pull request #51689 from vvoland/51688-docker-29.x
[docker-29.x backport] gha: Fix PR branch validation
2025-12-11 15:39:25 +00:00
Paweł Gronowski
ab55325b58 gha: Fix PR branch validation
Make it work with `docker-XYZ` branches.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit c74203adbb)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-11 16:35:09 +01:00
Paweł Gronowski
5c69198edd Merge pull request #51685 from vvoland/sync-29.x
[docker-29.x] Move to `29.1`
2025-12-11 15:25:58 +00:00
Paweł Gronowski
cbaccdaf6d Merge tag 'docker-v29.1.2' into docker-29.x
v29.1.2
2025-12-11 13:38:34 +01:00
Paweł Gronowski
09d5128bff Merge tag 'docker-v29.1.1' into docker-29.x
v29.1.1
2025-12-11 13:38:32 +01:00
Paweł Gronowski
b54adb2d03 Merge tag 'docker-v29.1.0' into docker-29.x
v29.1.0

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-11 13:37:59 +01:00
Sebastiaan van Stijn
de45c2ae4f Merge pull request #51648 from vvoland/update-go
update to go1.25.5
2025-12-02 21:58:47 +01:00
Sebastiaan van Stijn
4212eb0abf Merge pull request #51650 from thaJeztah/bump_actions
gha: update actions/checkout@v6, actions/upload-artifact@v5, actions/download-artifact@v6
2025-12-02 21:28:40 +01:00
Paweł Gronowski
6f9d1ec3fb update to go1.25.5
These releases include 2 security fixes following the security policy:

- crypto/x509: excessive resource consumption in printing error string for host certificate validation

    Within HostnameError.Error(), when constructing an error string, there is no limit to the number of hosts that will be printed out.
    Furthermore, the error string is constructed by repeated string concatenation, leading to quadratic runtime.

    Therefore, a certificate provided by a malicious actor can result in excessive resource consumption.
    HostnameError.Error() now limits the number of hosts and utilizes strings.Builder when constructing an error string.

    Thanks to Philippe Antoine (Catena cyber) for reporting this issue.

    This is CVE-2025-61729 and Go issue https://go.dev/issue/76445.

- crypto/x509: excluded subdomain constraint does not restrict wildcard SANs

    An excluded subdomain constraint in a certificate chain does not restrict the
    usage of wildcard SANs in the leaf certificate. For example a constraint that
    excludes the subdomain test.example.com does not prevent a leaf certificate from
    claiming the SAN *.example.com.

    This is CVE-2025-61727 and Go issue https://go.dev/issue/76442.

View the release notes for more information:
https://go.dev/doc/devel/release#go1.25.5

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-02 20:30:08 +01:00
Sebastiaan van Stijn
f132381992 Merge pull request #51649 from thaJeztah/bump_setup_action
gha: update to actions/setup-go@v6
2025-12-02 20:29:39 +01:00
Sebastiaan van Stijn
81d930f527 gha: update to actions/setup-go@v6
Includes a change to use go.dev/dl instead of storage.googleapis.com/golang
as fallback URL, because storage.googleapis.com/golang is deprecated.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-02 20:07:34 +01:00
Sebastiaan van Stijn
7000f92763 gha: update actions/download-artifact@v6
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-02 20:03:48 +01:00
Sebastiaan van Stijn
69963d84f8 gha: update actions/upload-artifact@v5
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-02 20:02:48 +01:00
Sebastiaan van Stijn
43ed81ed85 gha: update actions/checkout@v6
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-02 20:01:27 +01:00
Sebastiaan van Stijn
bced6f6100 Merge pull request #51647 from thaJeztah/bump_compress
vendor: github.com/klauspost/compress v1.18.2
2025-12-02 17:00:04 +01:00
Sebastiaan van Stijn
4b8f9dd251 vendor: github.com/klauspost/compress v1.18.2
No changes in vendored code

Fixes a regression in v1.18.1 that resulted in invalid flate/zip/gzip encoding.
The v1.18.1 tag has been retracted.

full diff: https://github.com/klauspost/compress/compare/v1.18.1...v1.18.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-02 16:45:38 +01:00
Paweł Gronowski
e4f1408738 Merge pull request #51628 from locnnil/patch-1
Dockerfile: Update buildx to 0.30.1
2025-12-02 15:23:36 +00:00
Paweł Gronowski
5ecc72679d Merge pull request #51645 from thaJeztah/api_relax_replace_check
hack/validate/module-replace: relax check
2025-12-02 15:23:02 +00:00
Sebastiaan van Stijn
7687298e0a hack/validate/module-replace: relax check
Do not require replace rules to be added if there's no code-changes
in the module. Note that changes in api/swagger.yaml may result in
changes in generated code, but this should be checked separate from
the swagger itself.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-02 16:14:07 +01:00
Paweł Gronowski
45be1a39b3 Merge pull request #51617 from vvoland/validate-share-image
gha/test-validate: Reuse the dev image
2025-12-02 15:13:03 +00:00
Lincoln Wallace
a828af4d8d Dockerfile: Update buildx to 0.30.1
Signed-off-by: Lincoln Wallace <lincoln.wallace@canonical.com>
2025-12-02 16:00:28 +01:00
Paweł Gronowski
616e53c12b Merge pull request #51633 from Xeeynamo/bump-runc-v134
Dockerfile: update runc binary to v1.3.4
2025-12-02 11:45:39 +00:00
Sebastiaan van Stijn
587d38292b Merge pull request #51629 from vvoland/c8d-fix-images
c8d/inspect: Fix image inspect for incomplete images
2025-12-02 12:35:27 +01:00
Luciano Ciccariello
f97f234729 Dockerfile: update runc binary to v1.3.4
- release notes: https://github.com/opencontainers/runc/releases/tag/v1.4.0
- full diff: opencontainers/runc@v1.3.3...v1.4.0

This version bump aims to fix a regression in runc v1.3.3, which caused
/dev/shm to have inappropriate permissions exposed to containers:
* https://github.com/opencontainers/runc/issues/4971
* https://github.com/opencontainers/runc/pull/4976

Signed-off-by: Luciano Ciccariello <xeeynamo@hotmail.com>
2025-12-02 09:31:00 +00:00
Brian Goff
a1836eb283 Merge pull request #51631 from thaJeztah/fix_df_shared_usage
system: df: fix SharedUsage on non-containerd
2025-12-01 13:30:07 -08:00
Paweł Gronowski
2e3a23c8ec c8d/inspect: Fix image inspect for incomplete images
When inspecting multi-platform images where some layer blobs were
missing from the content store, the image inspect operation would return
too early causing some data (like config details or unpacked size) to be
omitted even though are available.

This ensures that `docker image inspect` returns as much information as
possible.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-01 17:38:45 +01:00
Sebastiaan van Stijn
4ff8942d0d Merge pull request #51621 from robmry/fix-crash-with-nil-portbindings
PublishAllPorts: don't crash with nil PortBindings
2025-12-01 17:22:34 +01:00
Sebastiaan van Stijn
69c4ea7aad system: df: fix SharedUsage on non-containerd
The value was calculated, but due to 0af2962fdd
changing to a non-pointer, the value was not written back to the resulting
slice.

Before this patch:

    docker pull nginx:alpine
    docker pull alpine

    docker system df -v
    Images space usage:

    REPOSITORY   TAG       IMAGE ID       CREATED       SIZE      SHARED SIZE   UNIQUE SIZE   CONTAINERS
    nginx        alpine    cbad6347cca2   4 weeks ago   53.4MB    N/A           N/A           0
    alpine       latest    171e65262c80   7 weeks ago   8.51MB    N/A           N/A           0

With this patch:

    docker system df -v
    Images space usage:

    REPOSITORY   TAG       IMAGE ID       CREATED       SIZE      SHARED SIZE   UNIQUE SIZE   CONTAINERS
    nginx        alpine    cbad6347cca2   4 weeks ago   53.4MB    8.512MB       44.91MB       0
    alpine       latest    171e65262c80   7 weeks ago   8.51MB    8.512MB       0B            0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-12-01 16:45:38 +01:00
Paweł Gronowski
3964729182 Merge pull request #51622 from AkihiroSuda/fix-51602
dockerd-rootless-setuptool.sh: fix `nsenter: no namespace specified`
2025-12-01 10:06:50 +00:00
Akihiro Suda
8c0751aa4d dockerd-rootless-setuptool.sh: fix nsenter: no namespace specified
Fix issue 51602

Corresponds to https://github.com/containerd/nerdctl/blob/v2.2.0/extras/rootless/containerd-rootless-setuptool.sh#L654

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
2025-12-01 16:43:46 +09:00
Rob Murray
7517464283 PublishAllPorts: don't crash with nil PortBindings
Introduced by commit 85b260f ("PublishAllPorts: create
port mappings for exposed ports").

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-29 19:43:30 +00:00
Sebastiaan van Stijn
2faf258d4d Merge pull request #51616 from akerouanton/fix-51591
libnet/pms/nat: don't bind IPv6 ports if not supported by port driver
2025-11-29 00:54:16 +01:00
Albin Kerouanton
310aa9241a libnet/pm: log when stopping userland proxy
Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
2025-11-28 19:40:37 +01:00
Albin Kerouanton
52fae09ec0 libnet/pms/nat: don't bind IPv6 ports if not supported by port driver
In rootless mode, the Engine needs to call the rootless port driver to
know which IP address it should bind to inside of its network namespace.

The slirp4netns port drivers doesn't support binding to IPv6 address, so
we need to detect that before listening on the port.

Before commit 201968cc0, this wasn't a problem because the Engine was
binding the port, then calling rootless port driver to learn whether the
proto/IP family was supported, and listen on the port if so.

Starting with that commit, the Engine does bind + listen in one go, and
then calls the port driver — this is too late. Fix the bug by checking
if the port driver supports the PortBindingReq, and only allocate the
port if so.

Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
2025-11-28 19:40:34 +01:00
Paweł Gronowski
955650b33f gha/test-validate: Reuse the dev image
Don't build the dev image separately for each validation.

Build it once and then cache it so the validations can reuse it.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-28 16:26:29 +01:00
Sebastiaan van Stijn
9a84135d52 Merge pull request #51615 from akerouanton/revert-51507
Revert "libnet: setupDNS: don't overwrite user-modified resolv.conf"
2025-11-28 11:31:49 +01:00
Albin Kerouanton
56e8e43339 Revert "libnet: populateNetworkResourcesOS: updateDNS only if !needResolver"
This reverts commit 937246a868.

Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
2025-11-28 09:47:07 +01:00
Albin Kerouanton
83f00e9f2b Revert "libnet: rebuildDNS: update the hash file"
This reverts commit eb18b398d4.

Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
2025-11-28 09:47:04 +01:00
Albin Kerouanton
14a955db2f Revert "libnet: setupDNS: don't overwrite user-modified resolv.conf"
This reverts commit 7639e193ff.

Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
2025-11-28 09:47:01 +01:00
Rob Murray
710302ecf2 Merge pull request #51612 from robmry/client-v0.2.1
vendor: update to client 0.2.1
2025-11-27 16:38:00 +00:00
Rob Murray
4219768511 vendor: update to client 0.2.1
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-27 16:28:48 +00:00
Paweł Gronowski
b6f067c0cf Merge pull request #51607 from robmry/fix-api-vendor
client - use tagged api module
2025-11-27 16:45:56 +01:00
Rob Murray
ea539d267d client - use tagged api module
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-27 15:33:52 +00:00
Paweł Gronowski
e7cd814b67 Merge pull request #51610 from vvoland/validate-nofailfast
gha/validate: Actually dont fail fast
2025-11-27 16:31:17 +01:00
Paweł Gronowski
c74559df60 gha/validate: Actually dont fail fast
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-27 16:27:28 +01:00
Paweł Gronowski
e22cc91c8d Merge pull request #51609 from vvoland/validate-nofailfast
gha/validate: Don't fail fast
2025-11-27 16:17:47 +01:00
Paweł Gronowski
ecf4446e46 gha/validate: Don't fail fast
Allow other validate checks to finish even if one of them failed.

Sometimes a check is faulty and its failure is expected - in such case
we want to ignore that one validation fail but still run all the others.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-27 16:13:15 +01:00
Paweł Gronowski
e7d3eb855e Merge pull request #51608 from vvoland/validate-modulereplace-fix
validate/module-replace: Fix check
2025-11-27 16:10:11 +01:00
Paweł Gronowski
46ca7f19cd validate/module-replace: Fix check
The bash array usage was wrong - change to a simpler check that just
compares if the diff is empty.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-27 15:58:26 +01:00
Rob Murray
5a6be3fb51 Merge pull request #51606 from robmry/vendor-client-0.2.0
vendor: client/0.2.0
2025-11-27 14:12:11 +00:00
Rob Murray
f745fe7f14 vendor: client/0.2.0
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-27 14:05:10 +00:00
Paweł Gronowski
15a669176b Merge pull request #51604 from robmry/drop_replace
Drop replace rules
2025-11-27 14:32:11 +01:00
Rob Murray
a60bea5412 Drop replace rules
Prepare v29.1.0

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-27 13:15:03 +00:00
Paweł Gronowski
d55f77dbfc Merge pull request #51501 from thaJeztah/negotiate_default
client: enable API-version negotiation by default
2025-11-27 14:01:02 +01:00
Sebastiaan van Stijn
04ab3d562c client: don't downgrade when failing to negotiate
Historically, the client would downgrade to API v1.24 when failing
to negotiate as this was the API version from before API-version
negotiation was introduced.

Given that those daemons are EOL and those API versions no longer
supported, we should not fall back to an older API version, and
just continue using the latest / current version.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 13:52:27 +01:00
Sebastiaan van Stijn
189942570a client: enable API-version negotiation by default
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 13:52:27 +01:00
Sebastiaan van Stijn
e752ec0f8e client: fix typo in comment
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 13:52:24 +01:00
Paweł Gronowski
2eff406673 Merge pull request #51603 from thaJeztah/client_test_improvements
client: various test improvements
2025-11-27 13:46:47 +01:00
Albin Kerouanton
20634eddce Merge pull request #51496 from thaJeztah/discoverapi_cleanups
libnetwork: some minor refactor / cleanups
2025-11-27 12:22:01 +01:00
Sebastiaan van Stijn
bec7ab7f62 client: TestTLSCloseWriter: test with version negotiation enabled
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 12:05:27 +01:00
Sebastiaan van Stijn
701f2fdade client: improve mocking responses
Make the mocked responses match the API closer;

- Add headers as returned by the daemon's VersionMiddleware
- By default handle "/_ping" requests to allow the client to
  perform API-version negotiation as part of tests.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 12:05:27 +01:00
Sebastiaan van Stijn
ef588715b6 client: add mockPingResponse utility
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 12:05:26 +01:00
Sebastiaan van Stijn
acb5c5a390 client: mockResponse: prevent sharing body reader
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 12:05:26 +01:00
Sebastiaan van Stijn
45c9f460b8 client: checkResponseErr: don't read body for HEAD requests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 12:05:26 +01:00
Sebastiaan van Stijn
77858fab6e client: ensureBody: also ensure the request is preserved
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 12:05:26 +01:00
Sebastiaan van Stijn
e51a4306e2 client: ensureReaderClosed: small optimizations
Skip draining for HEAD requests and empty responses.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 12:05:26 +01:00
Sebastiaan van Stijn
89bd3150e1 client: client.ping(): use fresh request for HEAD -> GET
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-27 12:05:25 +01:00
Paweł Gronowski
c0c280ecf7 Merge pull request #51598 from thaJeztah/bump_zfs3
vendor: github.com/mistifyio/go-zfs/v3 v3.1.0
2025-11-26 21:46:38 +01:00
Paweł Gronowski
ea3011134b Merge pull request #51597 from vvoland/update-selinux
vendor: github.com/opencontainers/selinux v1.13.1
2025-11-26 21:43:45 +01:00
Sebastiaan van Stijn
2b8b692d3d Merge pull request #51600 from thaJeztah/no_empty_warnings
client: ServiceCreate,ServiceUpdate: don't add empty warnings
2025-11-26 21:14:48 +01:00
Sebastiaan van Stijn
de1f0ee351 Merge pull request #51595 from thaJeztah/bump_crypto
vendor: golang.org/x/crypto v0.45.0
2025-11-26 21:13:38 +01:00
Sebastiaan van Stijn
75520d1f5b client: resolveContainerSpecImage, resolvePluginSpecRemote: early returns
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-26 19:24:27 +01:00
Sebastiaan van Stijn
366ea9e9af client: ServiceCreate,ServiceUpdate: don't add empty warnings
This code was refactored in cd08b79c02, which
forgot to add a check for empty warnings.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-26 19:20:45 +01:00
Sebastiaan van Stijn
e94ed33de1 vendor: github.com/mistifyio/go-zfs/v3 v3.1.0
full diff: https://github.com/mistifyio/go-zfs/compare/v3.0.1...v3.1.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-26 17:45:58 +01:00
Paweł Gronowski
0389d3b13e vendor: github.com/opencontainers/selinux v1.13.1
full diff: https://github.com/opencontainers/selinux/compare/v1.13.0...v1.13.1

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-26 17:36:40 +01:00
Sebastiaan van Stijn
bda87b7de8 vendor: golang.org/x/crypto v0.45.0
full diff: https://github.com/golang/crypto/compare/v0.44.0...v0.45.0

Hello gophers,

We have tagged version v0.45.0 of golang.org/x/crypto in order to address two
security issues.

This version fixes a vulnerability in the golang.org/x/crypto/ssh package and a
vulnerability in the golang.org/x/crypto/ssh/agent package which could cause
programs to consume unbounded memory or panic respectively.

SSH servers parsing GSSAPI authentication requests don't validate the number of
mechanisms specified in the request, allowing an attacker to cause unbounded
memory consumption.

Thanks to Jakub Ciolek for reporting this issue.

This is CVE-2025-58181 and Go issue https://go.dev/issue/76363.

SSH Agent servers do not validate the size of messages when processing new
identity requests, which may cause the program to panic if the message is
malformed due to an out of bounds read.

Thanks to Jakub Ciolek for reporting this issue.

This is CVE-2025-47914 and Go issue https://go.dev/issue/76364.

Cheers, Go Security team

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-26 17:33:39 +01:00
Paweł Gronowski
c592d02dfc Merge pull request #51594 from thaJeztah/bump_x_deps
vendor: update various golang.org/x/xxx dependencies
2025-11-26 17:28:55 +01:00
Rob Murray
baf59d62d6 Merge pull request #51592 from robmry/sbleave_gw_config_error
Suppress errors from gateway re-config when disconnecting a network
2025-11-26 16:07:41 +00:00
Paweł Gronowski
b68e277ae8 Merge pull request #50903 from dmcgowan/c8d-fix-window-migration-panic
Fix panic on Windows when containerd is not enabled and snapshotter is
2025-11-26 15:58:31 +01:00
Rob Murray
163cc95aea Add TestGatewayErrorOnNetDisconnect
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-26 14:45:18 +00:00
Rob Murray
8f2aa3e0f5 Network disconnect: log rather than error on gateway update
During a network disconnect, log rather than returning an error
if it's not possible to set up a new gateway.

This restores the behaviour from before commit 53390f8 ("Put
clearNetworkResources() inline in its only caller"). It's not
ideal, but by the time new gateways are selected the old
endpoint has been disconnected - and nothing puts things back.
Until that's cleaned up, a broken state is inevitable, but
letting endpoint deletion complete means the container can
be restarted or re-connected to the network without a zombie
endpoint causing further issues.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-26 14:45:18 +00:00
Derek McGowan
e4a1657762 Check containerd client before using on Windows
Throw an error if the containerd snapshotter is enabled on Windows but
containerd has not been configured. This fixes a panic in this case when
trying to use an uninitialized client.

Signed-off-by: Derek McGowan <derek@mcg.dev>
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-26 15:40:08 +01:00
Sebastiaan van Stijn
b1dccad684 Merge pull request #51593 from thaJeztah/bump_circl
vendor: github.com/cloudflare/circl v1.6.1
2025-11-26 15:03:32 +01:00
Sebastiaan van Stijn
a3916290da vendor: golang.org/x/mod v0.30.0
full diff: https://github.com/golang/mod/compare/v0.29.0...v0.30.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-26 14:31:44 +01:00
Sebastiaan van Stijn
10d68d4399 vendor: golang.org/x/net v0.47.0
full diff: https://github.com/golang/net/compare/v0.46.0...v0.47.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-26 14:15:14 +01:00
Sebastiaan van Stijn
633acaa5b3 vendor: golang.org/x/crypto v0.44.0
full diff: https://github.com/golang/crypto/compare/v0.43.0...v0.44.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-26 14:14:35 +01:00
Sebastiaan van Stijn
e4900958c3 vendor: golang.org/x/text v0.31.0
full diff: https://github.com/golang/text/compare/v0.30.0...v0.31.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-26 14:09:23 +01:00
Sebastiaan van Stijn
bd79eb0da5 vendor: golang.org/x/tools v0.38.0
full diff: https://github.com/golang/tools/compare/v0.37.0...v0.38.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-26 14:08:41 +01:00
Sebastiaan van Stijn
421bda22d1 vendor: golang.org/x/sync v0.18.0
full diff: https://github.com/golang/sync/compare/v0.17.0...v0.18.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-26 13:43:51 +01:00
Sebastiaan van Stijn
3054fdd8b2 vendor: golang.org/x/sys v0.38.0
- cpu: add HPDS, LOR, PAN detection for arm64
- cpu: also use MRS instruction in getmmfr1
- cpu: use MRS instruction to read arm64 system registers
- unix: add consts for ELF handling
- unix: add SetMemPolicy and its mode/flag values
- unix: add SizeofNhmsg and SizeofNexthopGrp
- windows: add iphlpapi routing functions

full diff: https://github.com/golang/sys/compare/v0.37.0...v0.38.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-26 13:39:03 +01:00
Sebastiaan van Stijn
997837376a vendor: github.com/cloudflare/circl v1.6.1
- fixes [GHSA-2x5j-vhc8-9cwm]: CIRCL-Fourq: Missing and wrong validation
  can lead to incorrect results

full diff: https://github.com/cloudflare/circl/compare/v1.6.0...v1.6.1

[GHSA-2x5j-vhc8-9cwm]: https://github.com/cloudflare/circl/security/advisories/GHSA-2x5j-vhc8-9cwm

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-26 13:30:29 +01:00
Rob Murray
5512eea3d0 Merge pull request #51559 from 2003Aditya/TestAPIImagesSaveAndLoad
migrate TestAPIImagesSaveAndLoad to integration test
2025-11-26 12:11:14 +00:00
Rob Murray
a3c29e8996 Merge pull request #51455 from smerkviladze/add-windows-integration-tests
integration: add Windows network driver and isolation tests
2025-11-26 11:42:54 +00:00
Rob Murray
4a166c7316 Merge pull request #51459 from 2003Aditya/contributing-docs
docs: update contributing guides for clarity and consistency (#49891)
2025-11-26 10:57:39 +00:00
Sopho Merkviladze
5a5d5b4c5f integration: add Windows network driver and isolation tests
Add integration tests for Windows container functionality focusing on network drivers and container isolation modes.

Signed-off-by: Sopho Merkviladze <smerkviladze@mirantis.com>
2025-11-26 13:35:19 +04:00
Rob Murray
18d2a08fcf Merge pull request #51587 from robmry/unmap_ipv6_addr
Unmap() more netip.Addr vars created from slices
2025-11-25 19:30:27 +00:00
Rob Murray
731ae07e65 Endpint.sbLeave: when deleting container, no new gateway
When the endpoint providing a container's default gateway
is removed, there's no need to select a new gateway if the
container is being removed.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-25 18:11:07 +00:00
Albin Kerouanton
03797acd88 Merge pull request #51586 from robmry/ep_options_exposed_ports
PublishAllPorts: create port mappings for exposed ports
2025-11-25 18:21:59 +01:00
Rob Murray
abd4c104dc Merge pull request #51526 from robmry/refactor-create-mounts
Refactor Daemon.create - prep for call to NRI plugin
2025-11-25 16:13:57 +00:00
Rob Murray
56dda25227 Merge pull request #51507 from zhangguanzhang/fix-pause-restart
libnet: setupDNS: don't overwrite user-modified resolv.conf
2025-11-25 13:53:10 +00:00
zhangguanzhang
7639e193ff libnet: setupDNS: don't overwrite user-modified resolv.conf
Call resolvconf.UserModified() in sandbox.setupDNS() to check if
resolv.conf was manually modified before regenerating it during
container restart for non-host network modes.

Signed-off-by: zhangguanzhang <zhangguanzhang@qq.com>
Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
2025-11-25 20:32:40 +08:00
Albin Kerouanton
eb18b398d4 libnet: rebuildDNS: update the hash file
Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
2025-11-25 11:51:27 +01:00
Albin Kerouanton
937246a868 libnet: populateNetworkResourcesOS: updateDNS only if !needResolver
When ep.needResolver() is true, sb.startResolver() calls sb.rebuildDNS()
which doesn't update the resolv.conf hash file.

Subsequent calls to sb.updateDNS() (which is only called by
populateNetworkResourcesOS) won't have any effect since it'll compare
the hash file and consider that the file was manually modified.

Make this explicit by gating the call to updateDNS() on !needResolver().

Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
2025-11-25 11:43:36 +01:00
Rob Murray
85b260fba8 PublishAllPorts: create port mappings for exposed ports
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-24 20:38:46 +00:00
Paweł Gronowski
4612690e23 Merge pull request #51583 from vvoland/51577-docker-29.x
[docker-29.x backport] Allow configured address with no configured subnet
2025-11-24 18:44:28 +00:00
Brian Goff
f7ccfed3f3 Merge pull request #51572 from corhere/otelcol-windows
.github: capture OTEL trace spans from all processes
2025-11-24 10:38:07 -08:00
Rob Murray
a2de9bb334 Unmap more netip.Addr vars created using AddrFromSlice
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-24 16:51:14 +00:00
Rob Murray
9907ded7df Merge pull request #51577 from robmry/static-ip-no-configured-subnet
Allow configured address with no configured subnet
2025-11-24 16:37:27 +00:00
Rob Murray
6280a80f32 Allow configured address with no configured subnet
Signed-off-by: Rob Murray <rob.murray@docker.com>
(cherry picked from commit 84a251d039)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-24 17:33:10 +01:00
Rob Murray
9cbafeac46 Update docker-py in test-docker-py
Pick up fixes for:
- test_create_with_ipv6_address
- test_connect_with_ipv6_address

65f7f0c..df3f8e2

Signed-off-by: Rob Murray <rob.murray@docker.com>
(cherry picked from commit 7e14b4d931)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-24 17:33:07 +01:00
Rob Murray
2effc1bfd4 inspect: unmap IPv6-mapped IPv4 host address
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-24 16:29:32 +00:00
Rob Murray
84a251d039 Allow configured address with no configured subnet
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-24 14:14:27 +00:00
Rob Murray
7e14b4d931 Update docker-py in test-docker-py
Pick up fixes for:
- test_create_with_ipv6_address
- test_connect_with_ipv6_address

65f7f0c..df3f8e2

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-24 14:14:00 +00:00
Cory Snider
517ae20be8 .github: collect all the OTEL traces
Jaeger does not make it easy to dump all the collected trace spans from
all services at once. Switch to using the OpenTelemetry Collector with
the OTLP File exporter which writes the traces straight to disk in a
format that Jaeger UI can natively consume.

Signed-off-by: Cory Snider <csnider@mirantis.com>
2025-11-21 18:12:25 -05:00
Brian Goff
6e52828ec3 Merge pull request #51517 from corhere/opencensus-bridge
daemon: install OpenCensus-to-OTEL trace bridge
2025-11-20 11:07:51 -08:00
Paweł Gronowski
45169d5784 Merge pull request #51571 from tonistiigi/update-buildkit-v0.26.2
vendor: update buildkit to v0.26.2
2025-11-20 16:22:10 +00:00
Tonis Tiigi
54d269a3b4 vendor: update buildkit to v0.26.2
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2025-11-20 13:27:49 +00:00
2003Aditya
442f0115cd docs: update contributing guides for clarity and consistency (#49891)
Signed-off-by: Aditya Mishra <mishraaditya675@gmail.com>
2025-11-20 18:03:02 +05:30
Aditya Mishra
93825e00e8 migrate TestAPIImagesSaveAndLoad to integration test
Signed-off-by: Aditya Mishra <mishraaditya675@gmail.com>
2025-11-20 17:39:10 +05:30
Sebastiaan van Stijn
814cc31ff3 Merge pull request #51557 from robmry/replace_lock_in_remote_nw_driver
Remote nw driver: restore missing nwEndpointsMu.Lock
2025-11-19 16:35:48 +00:00
Rob Murray
ed10b98506 Restore missing nwEndpointsMu.Lock
- introduced by 4f7afb8 (Remove libnet's logic to track a driver's
  port mapping state)

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-18 15:38:48 +00:00
Sebastiaan van Stijn
c36b8ddd57 Merge pull request #51550 from 2003Aditya/TestAPIGetEnabledCORS
integration: migrate TestAPIGetEnabledCORS to integration test
2025-11-18 13:25:10 +00:00
Aditya Mishra
9fbc9d6e6d integration: migrate TestAPIGetEnabledCORS to integration test
Signed-off-by: Aditya Mishra <mishraaditya675@gmail.com>
2025-11-18 12:20:20 +05:30
Paweł Gronowski
41ff290929 Merge pull request #51551 from tonistiigi/update-buildkit-v0.26.1
vendor: update buildkit to v0.26.1
2025-11-17 17:28:30 +00:00
Paweł Gronowski
33e967575d Merge pull request #51549 from thaJeztah/rm_TestVolumeCLINoArgs
integration-cli: remove TestVolumeCLINoArgs
2025-11-17 17:03:43 +00:00
Paweł Gronowski
931784650d Merge pull request #51533 from thaJeztah/client_refactor_negotiate
client: simplify logic for manual vs auto API versions
2025-11-17 16:39:11 +00:00
Tonis Tiigi
774bb532f1 vendor: update buildkit to v0.26.1
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2025-11-17 16:16:43 +00:00
Paweł Gronowski
46e28a284a Merge pull request #51539 from thaJeztah/fix_content_type
client: Client.buildRequest: fix content-type handling
2025-11-17 16:14:49 +00:00
Sebastiaan van Stijn
237446a25d Merge pull request #51548 from thaJeztah/improve_TestTLSCloseWriter
client: TestTLSCloseWriter: assorted cleanups / fixes
2025-11-17 11:17:22 +01:00
Sebastiaan van Stijn
e59d1b4563 libnetwork/drivers/overlay: DiscoverNew: move logic to setKeys, updateKeys
Make the DiscoverNew switch only responsible for asserting the correct
data type, and push the conversion logic into the setKeys and updateKeys
methods.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-16 23:36:04 +01:00
Sebastiaan van Stijn
f40b45ca1f libnetwork/drivers/overlay: use structured logs in some places
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-16 23:36:04 +01:00
Sebastiaan van Stijn
c9f0314f21 libnetwork: controller.handleKeyChange: slight cleanup of logs
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-16 23:36:03 +01:00
Sebastiaan van Stijn
a03d6bd071 Merge pull request #51536 from locnnil/patch-1
Dockerfile: update cli to v29.0.1
2025-11-16 23:33:24 +01:00
Sebastiaan van Stijn
cf7cc93529 Merge pull request #51498 from 2003Aditya/TestAPIImagesDelete
Test api images delete
2025-11-16 23:04:06 +01:00
Sebastiaan van Stijn
029770595d integration-cli: remove TestVolumeCLINoArgs
This test was only testing behavior of the CLI itself (or even basic
functionality provided by Cobra).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-16 21:21:56 +01:00
Sebastiaan van Stijn
9ab033cc8a client: TestTLSCloseWriter: assorted cleanups / fixes
- close idle connections after test
- don't discard failures to write upgrade response
- ignore errors in defer to make the linters happy

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-16 20:56:12 +01:00
Rob Murray
96b8f9c8ca Daemon.createContainerOSSpecificSettings - remove redundant param
Also:
- remove the hostConfig param from Daemon.createContainerVolumesOS.
- rename var container -> ctr

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-16 18:25:42 +00:00
Rob Murray
33032b0454 Daemon.setSecurityOptions: remove redundant param
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-16 18:25:06 +00:00
Rob Murray
34925e5be9 Remove Daemon.setHostConfig
The container's constructor, Daemon.newContainer, already has
hostConfig and can just assign it directly.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-16 18:25:06 +00:00
Rob Murray
22c5c78bfb Move Daemon.registerMountPoints out of Daemon.setHostConfig
Call registerMountPoints after the rest of the container's
configuration has been set up.

This will make it possible to call an NRI plugin with the
container's config, allowing it to adjust the mounts in that
config, before it's used to find volumes etc.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-16 18:25:06 +00:00
Rob Murray
48709e502f Split OS-specific container config and volume creation
Daemon.createContainerOSSpecificSettings adds container config for
the OS, and creates volumes. Split those two things.

This will make it possible to call an NRI plugin after the config
is complete, before volumes are created - so the NRI plugin can
adjust a full set of config, including volumes.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-16 18:20:12 +00:00
Rob Murray
4434236088 Daemon.setHostConfig - don't set default network mode
It's set later in Daemon.create, setHostConfig's only caller.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-16 18:20:12 +00:00
Rob Murray
e757bbb4ea Move call to Daemon.registerLinks out of Daemon.setHostConfig
The call from Daemon.create -> Daemon.setHostConfig acquired
container.Lock, but didn't need to because the container is
newly created and solely owned by the caller. The call from
Daemon.restore did not acquire the lock.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-16 18:20:12 +00:00
Rob Murray
92b4902b8d Daemon.registerMountPoints: var 'container' -> 'ctr'
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-16 18:19:56 +00:00
Sebastiaan van Stijn
17552fc792 Merge pull request #51542 from robmry/rootless-noipv6
rootless: ignore error when enabling IPv6 forwarding
2025-11-16 14:20:19 +01:00
Rob Murray
5c9f2e0388 rootless: ignore error when enabling IPv6 forwarding
For hosts with IPv6 disabled.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-16 11:51:14 +00:00
Sebastiaan van Stijn
4622dd0ccc client: Client.buildRequest, jsonEncode improve handling of content
- add early returns for `nil` body, `http.NoBody`, and `json.RawMessage`
- use `http.NoBody` instead of `nil` for empty bodies; it's more clear
  on intent.
- use json.Encode instead of json.Encoder.Encode(), as we're marshaling
  a single JSON document; this also avoid adding a trailing newline.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-15 00:39:59 +01:00
Sebastiaan van Stijn
20d65620f9 client: Client.buildRequest: don't set content-header if not set
This function was setting `text/plain` as default content-type for any
request that had a non-nil body.

However, this would also set the content-type if (e.g.) `http.NoBody` was set,
or if an empty reader was used, which would result in the daemon potentialy
rejecting the request, as it validates request to be using `application/json`;
d9ee22d1ab/daemon/server/httputils/httputils.go (L47-L58)

    === RUN   TestCommitInheritsEnv
        commit_test.go:30: assertion failed: error is not nil: Error response from daemon: unsupported Content-Type header (text/plain): must be 'application/json'
    --- FAIL: TestCommitInheritsEnv (0.02s)

This patch removes setting the default content-type.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-15 00:39:59 +01:00
Sebastiaan van Stijn
a81d441133 Merge pull request #51534 from robmry/dont-remove-removed-gateway
Don't try to remove a cleared docker_gwbridge endpoint
2025-11-15 00:32:32 +01:00
Lincoln Wallace
b17eee7aad Update DOCKERCLI_VERSION to v29.0.1
Signed-off-by: Lincoln Wallace <lincoln.wallace@canonical.com>
2025-11-14 16:38:36 -03:00
Sebastiaan van Stijn
4e2e2cde7e client: simplify logic for manual vs auto API versions
When manually setting the API version to use, automatic API version
negotiation should no longer be performed. Instead of keeping track
of these options individually, we can mark negotiation to have happend
if either the version was set manually, or if API version negotiation
took place.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-14 20:30:49 +01:00
Sebastiaan van Stijn
d9ee22d1ab Merge pull request #51530 from thaJeztah/allow_renegotiate
client: client.Ping: allow ForceNegotiate with manual override
2025-11-14 20:07:27 +01:00
Rob Murray
1731e9e729 Don't try to remove cleared docker_gwbridge endpoint
If a container is using a docker_gwbridge endpoint as its gateway,
when it's connected to another network that provides a gateway, the
docker_gwbridge endpoint is removed when that endpoint is added (in
a recursive nightmare).

So, the "before" gateway for the container has been removed
before the new gateway is updateExternalConnectivity'd.

Don't pass the old gateway to updateExternalConnectivity in that
case, because the network driver's already forgotten about it.

Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-11-14 17:41:01 +00:00
Sebastiaan van Stijn
f156a683b0 Merge pull request #51527 from thaJeztah/validate_versions
client: improve validation and handling of WithAPIVersion, WithAPIVersionFromEnv
2025-11-14 18:17:13 +01:00
Sebastiaan van Stijn
6857132911 client: client.Ping: allow ForceNegotiate with manual override
While a manual overridden version shouldn't perform automatic version
negotiation, the "ForceNegotiate" option could still be used to (re)
negotiate a version. This allows a client to be configured with an
initial API version, then triggered to perform API-version negotiation.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-14 17:41:56 +01:00
Sebastiaan van Stijn
c1e217d18d Merge pull request #51529 from thaJeztah/skip_TestBuildWithHugeFile
integration: skip TestBuildWithHugeFile
2025-11-14 17:35:48 +01:00
Akihiro Suda
57e8ef9d30 Merge pull request #51484 from thaJeztah/vendor_oci_cgroups
vendor: github.com/opencontainers/cgroups v0.0.6
2025-11-14 11:32:06 -05:00
Aditya Mishra
8f1134b46d integration/image: migrate TestAPIImagesDelete to the new integration test framework
Migrated TestAPIImagesDelete from the legacy integration-cli suite
(docker_api_images_test.go) to the new integration test framework under
integration/image/remove_test.go.

This update:
- Fixes ENV instruction syntax to use "ENV FOO=bar"
- Adds error type check using errdefs.IsNotFound for cleaner assertions
- Ensures consistent cleanup handling

Signed-off-by: Aditya Mishra <mishraaditya675@gmail.com>
2025-11-14 22:00:15 +05:30
Sebastiaan van Stijn
3e4a3cb03e integration: skip TestBuildWithHugeFile
We've seen various failures recently where GitHub actions runners are
running out of space. Skip this test for now.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-14 17:23:05 +01:00
Albin Kerouanton
c151d52562 Merge pull request #51525 from akerouanton/revendor-ishidawataru-sctp
vendor: github.com/ishidawataru/sctp v0.0.0-20251114114122-1
2025-11-14 16:05:37 +01:00
Sebastiaan van Stijn
53764de815 client: make WithAPIVersion, WithAPIVersionFromEnv order-independent
Environment-variables are expected to override config / defaults, so
make sure that the DOCKER_API_VERSION env-var always takes priority.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-14 13:54:32 +01:00
Sebastiaan van Stijn
a5c7f3f9c8 client: don't negotiate malformed responses
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-14 13:54:31 +01:00
Sebastiaan van Stijn
bcc1db1ce5 client: WithAPIVersion, WithAPIVersionFromEnv: validate well-formedness
Make these options more strict to not allow arbitrary values. Historically,
the `DOCKER_API_VERSION` env-var did not perform any validation as it was
intended for testing-purposes, but given the wider use of this env-var,
we should perform some amount of validation.

Both `WithAPIVersion` and `WithAPIVersionFromEnv` still allow specifying
API versions that are not supported by the client for testing purposes
(e.g. to use API versions beyond `MinAPIVersion` and `MaxAPIVersion`),
but must be well-formed.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-14 13:54:31 +01:00
Sebastiaan van Stijn
83ad5c92f7 client: Client.ping() fix duplicate ping
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-14 13:54:27 +01:00
Sebastiaan van Stijn
b98c41f124 Merge pull request #51520 from thaJeztah/rename_api_version
client: export fallbackAPIVersion as MinAPIVersion
2025-11-14 13:05:09 +01:00
Albin Kerouanton
49c8d77639 vendor: github.com/ishidawataru/sctp v0.0.0-20251114114122-1
full diff: 4b890084db..19ddcbc6aa

Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
2025-11-14 12:59:14 +01:00
Albin Kerouanton
ddc3603647 Merge pull request #51425 from thaJeztah/no_pointer_slice_step2
daemon: reduce use of pointer-slices in backend
2025-11-14 12:01:45 +01:00
Sebastiaan van Stijn
611c7dce43 client: export fallbackAPIVersion as MinAPIVersion
Export the const and rename it to better reflect its intent.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-14 10:26:37 +01:00
Sebastiaan van Stijn
16880e9e1b Merge pull request #51512 from thaJeztah/client_test_cleanups
client: assorted test-cleanups and fixes
2025-11-13 21:01:42 +01:00
Cory Snider
4535d63c91 daemon: install OpenCensus-to-OTEL trace bridge
Export trace spans from the github.com/microsoft/hcsshim module, which
is instrumented with OpenCensus, to the daemon's OpenTelemetry exporter
to provide more visibility into Windows container lifecycle operations.

Signed-off-by: Cory Snider <csnider@mirantis.com>
2025-11-13 15:00:05 -05:00
Sebastiaan van Stijn
0af2962fdd daemon: reduce use of pointer-slices in backend
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-13 20:52:15 +01:00
Paweł Gronowski
feba59eccd Merge pull request #51492 from vvoland/c8d-fix-selection
daemon: Fix image store choice priority for prior graphdriver state
2025-11-13 20:19:25 +01:00
Albin Kerouanton
d494784654 Merge pull request #51495 from akerouanton/revert-a8b9eff90
libnet: create DNS records on sbJoin (if not agent node)
2025-11-13 20:02:28 +01:00
Albin Kerouanton
53ea70ea46 inte/networking: TestDisableIPv6OnInterface: add '-c1' to ping
If the DNS name still resolves to an IP address, and that address is
assigned to a running container, the ping command will run indefinitely
and the test suite will time out for 10 mins.

This is confusing, as it looks like a daemon hang, or a test suite hang,
whereas it's just a test failure. Add '-c1' to ping to make it return
immediately.

Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
2025-11-13 18:42:11 +01:00
Albin Kerouanton
47bd247d4d inte/networking: test DNS resolution for non swarm-scoped nws
Previous commit reverted a faulty change that broke DNS resolution for
non swarm-scoped networks once a node has joined a Swarm cluster.

This commit adds an integration test to verify that we don't break DNS
resolution again.

Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
2025-11-13 18:42:11 +01:00
Albin Kerouanton
2e41476a5f libnet: create DNS records on sbJoin (if not agent node)
Commit a8b9eff90 removed a call to Network.updateSvcRecord from
Network.createEndpoint on the grounds that:

> all callers of Network.createEndpoint follow up with an Endpoint.Join,
> which also sets up the DNS entry.

However, the original call in Network.createEndpoint was gated by:

```
if !n.getController().isSwarmNode() || n.Scope() != scope.Swarm || !n.driverIsMultihost() {
	n.updateSvcRecord(context.WithoutCancel(ctx), ep, true)
}
```

whereas the call in Endpoint.sbJoin() (invoked by Endpoint.Join()) is
gated by:

```
if !n.getController().isAgent() {
    if !n.getController().isSwarmNode() || n.Scope() != scope.Swarm || !n.driverIsMultihost() {
	    n.updateSvcRecord(context.WithoutCancel(ctx), ep, true)
    }
}
```

As a result, once a node has joined a Swarm cluster, no DNS entries are
created for non swarm-scoped networks.

Change the condition used by `sbJoin` to match the original condition
used in `createEndpoint`.

Signed-off-by: Albin Kerouanton <albin.kerouanton@docker.com>
2025-11-13 17:31:13 +01:00
Sebastiaan van Stijn
94e83af71a client: TestImageListWithSharedSize: merge with TestImageList
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-13 15:57:24 +01:00
Sebastiaan van Stijn
62589a6961 client: TestTLSCloseWriter: cancel context after test
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-13 15:46:54 +01:00
Sebastiaan van Stijn
a5dec0a779 client: make sure context is canceled for ContainerWait tests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-13 15:44:09 +01:00
Sebastiaan van Stijn
76a4381d45 client: TestImageListWithSharedSize slight reformat
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-13 15:43:13 +01:00
Sebastiaan van Stijn
9af7fbff2a client: TestImageList: use subtests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-13 15:43:13 +01:00
Sebastiaan van Stijn
7a3b88d91c Merge pull request #51500 from thaJeztah/client_rename_api_options
client: rename/deprecate WithVersion, WithVersionFromEnv
2025-11-13 15:19:08 +01:00
Sebastiaan van Stijn
4a57a8cd7c Merge pull request #51504 from vvoland/hack-check-module-replace
hack/validate: Check if replace rules are needed
2025-11-13 15:17:11 +01:00
Sebastiaan van Stijn
f95781f0d2 Merge pull request #51506 from jsternberg/vendor-buildkit
vendor: github.com/moby/buildkit v0.26.0
2025-11-13 13:59:02 +01:00
Sebastiaan van Stijn
a6b42c090b Merge pull request #51503 from vvoland/fix-apiclient-unitest
hack/test/unit: Fix api and client module testing without replace rules
2025-11-13 13:54:06 +01:00
Paweł Gronowski
391247ce96 daemon: Fix image store choice priority for prior graphdriver state
The priority order for determining image store choice was incorrect when
a prior graphdriver existed.

The issue occurred because the prior graphdriver check happened after
processing explicit driver configuration, effectively ignoring user
intent when prior state existed.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-13 10:05:49 +01:00
Paweł Gronowski
c5d0e3e6fa daemon: Add TestDetermineImageStoreChoice
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-13 10:05:49 +01:00
Jonathan A. Sternberg
17a3357e32 vendor: github.com/moby/buildkit v0.26.0
Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-11-12 15:53:45 -06:00
Sebastiaan van Stijn
dae3650dcc client: rename/deprecate WithVersion, WithVersionFromEnv
Add WithAPIVersion and WithAPIVersionFromEnv to be more clear on
the intent, and to align with other related options and fields.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-12 22:39:05 +01:00
Sebastiaan van Stijn
e5db2380f5 client: rename options.go to client_options.go
Make sure the options are next to the client.go file, which use the
consumer of these options.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-12 22:39:02 +01:00
Paweł Gronowski
5cf1fb3954 hack/validate: Check if replace rules are needed
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-12 21:43:24 +01:00
Paweł Gronowski
0f597561e8 hack/test/unit: Fix api and client module testing without replace rules
Running sub-package tests from the root module without readding the
replace rules wasn't running the tests from the local in-tree versions
of these submodules.

Fix by cd-ing into their directories before running tests.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-12 21:38:59 +01:00
Albin Kerouanton
27cefe6c43 Merge pull request #51499 from thaJeztah/fix_example
client: fix example in README (align with ExampleNew())
2025-11-12 21:05:52 +01:00
Sebastiaan van Stijn
2729703967 client: fix example in README (align with ExampleNew())
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-12 20:31:04 +01:00
Sebastiaan van Stijn
8f572f33fd Merge pull request #51502 from thaJeztah/fix_imports
client: fix unused imports
2025-11-12 20:22:00 +01:00
Sebastiaan van Stijn
9824080b57 Merge pull request #51012 from tonistiigi/attestation-signature-referrers
image: pull/load/save attestation manifest and signatures with image
2025-11-12 18:04:21 +01:00
Sebastiaan van Stijn
b29990916d client: fix unused imports
this was introduced in c950796596, but
likely due to the "replace" rules not being present, CI tested the
current version of the module instead of the code in the repository.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-12 17:53:36 +01:00
Sebastiaan van Stijn
b2d01d9907 Merge pull request #51497 from thaJeztah/add_replace
go.mod: add back replace rules
2025-11-12 17:23:54 +01:00
Tonis Tiigi
47e852f061 image: pull/load/save attestation manifest and signatures with image
Updates docker pull to pull related attestation manifest and
any signatures for that manifest in cosign referrer objects.

These objects are transferred with the image when running
docker save and docker load and can be used to identify
the image in future updates.

Push is not updated atm as the currect push semantics
in containerd mode do not have correct immutability
guaranteed and don't work with image indexes.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2025-11-12 07:53:46 -08:00
Paweł Gronowski
70cd42fd30 Merge pull request #51493 from vvoland/c8d-build-noforceunpack
c8d/builder-next: Don't force unpack
2025-11-12 16:39:52 +01:00
Sebastiaan van Stijn
16b95ba758 go.mod: add back replace rules
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-12 15:43:16 +01:00
Paweł Gronowski
b4f9bd1cb3 c8d/builder-next: Don't force unpack
The image exporter wrapper was unconditionally setting `unpack=true` for
all build exports, preventing users from controlling this behavior
through buildkit's output image exporter option.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-11-12 14:34:12 +01:00
Paweł Gronowski
e7d7771bce Merge pull request #51483 from thaJeztah/rm_MinConnectTimeout
daemon: remove workaround for c8d client connection timeout
2025-11-12 11:01:16 +01:00
Sebastiaan van Stijn
fb2ca99227 Merge pull request #51411 from tonistiigi/update-buildkit-v0.26.0-rc1
vendor: update buildkit v0.26.0-rc2
2025-11-12 01:05:05 +01:00
Tonis Tiigi
3874ca5984 vendor: update buildkit to v0.26.0-rc2
Currently requires replace rule for swarmkit etcd.

BuildKit itself doesn't use etcd but version gets bumped
via unused dependency.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 23:15:07 +01:00
Sebastiaan van Stijn
d558896fae vendor: github.com/secure-systems-lab/go-securesystemslib v0.9.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 23:14:54 +01:00
Sebastiaan van Stijn
bb07fdcd14 vendor: github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 23:14:54 +01:00
Sebastiaan van Stijn
f1d0fe47c9 vendor: github.com/golang-jwt/jwt/v5 v5.3.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 23:14:54 +01:00
Sebastiaan van Stijn
79344e1c9a vendor: github.com/gofrs/flock v0.13.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 23:14:53 +01:00
Sebastiaan van Stijn
d36617d2c1 vendor: github.com/containerd/nydus-snapshotter v0.15.4
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 23:14:53 +01:00
Sebastiaan van Stijn
1e48c34345 vendor: github.com/containerd/stargz-snapshotter/estargz v0.17.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 23:14:53 +01:00
Sebastiaan van Stijn
c169cc9629 vendor: github.com/google/certificate-transparency-go v1.3.2
full diff: https://github.com/google/certificate-transparency-go/compare/v1.1.4...v1.3.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 23:14:53 +01:00
Sebastiaan van Stijn
940c8d6b71 vendor: cloud.google.com/go v0.121.6
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 23:14:52 +01:00
Rob Murray
dbbfb4f90d Merge pull request #51486 from locnnil/bump-docker-cli
Dockerfile: update cli to v29.0.0
2025-11-11 22:11:11 +00:00
Lincoln Wallace
621cee8dbe Dockerfile: update cli to v29.0.0
Signed-off-by: Lincoln Wallace <lincoln.wallace@canonical.com>
2025-11-11 17:09:46 -03:00
Sebastiaan van Stijn
ce739870fb vendor: github.com/opencontainers/cgroups v0.0.6
- config: switch PidsLimit to *int64
- fs2: add iocost statistics
- systemd: retry when the dbus connection returns EAGAIN
- fs: fix/improve cpuacct.usage_all parsing

full diff: https://github.com/opencontainers/cgroups/compare/v0.0.5...v0.0.6

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 19:23:01 +01:00
Sebastiaan van Stijn
7e78088f8f daemon: remove workaround for c8d client connection timeout
This workaround was added in df519e9e1a, pending
a fix in containerd;

> daemon: Fix giving up too early while connecting to containerd socket
>
> Explicitly set the gRPC connection params to take the timeout into
> account to workaround the containerd v2 client not passing down the
> stack.
>
> containerd v2 replaced usages of deprecated gRPC functions but didn't
> pass the timeout to the actual dial connection options.

A fix for this was merged in [containerd@ee574e7], which is part of containerd
v2.1.0-beta.0, and backported to containerd v2.0.4 through [containerd@6b5efba].

[containerd@ee574e7]: ee574e76e7
[containerd@6b5efba]: 6b5efba83b

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 17:55:44 +01:00
Sebastiaan van Stijn
07453d15d0 Merge pull request #51482 from thaJeztah/vendor_containerd_2.2.0
vendor: github.com/containerd/containerd/v2 v2.2.0
2025-11-11 17:35:23 +01:00
Sebastiaan van Stijn
f740e0fefa vendor: github.com/containerd/containerd/v2 v2.2.0
full diff: https://github.com/containerd/containerd/compare/v2.1.5...v2.2.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 16:13:34 +01:00
Sebastiaan van Stijn
1639703e56 vendor: github.com/containernetworking/plugins v1.8.0
full diff: https://github.com/containernetworking/plugins/compare/v1.7.1...v1.8.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 16:13:31 +01:00
Sebastiaan van Stijn
e4278c4c54 vendor: github.com/containerd/go-cni v1.1.13
full diff: https://ithub.com/containerd/go-cni/compare/v1.1.12...v1.1.13

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 16:12:25 +01:00
Sebastiaan van Stijn
7c798d012a vendor: sigs.k8s.io/yaml v1.6.0
full diff: https://github.com/kubernetes-sigs/yaml/compare/v1.4.0...v1.6.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 16:12:25 +01:00
Sebastiaan van Stijn
d8f2aa4e3b vendor: github.com/containerd/containerd/api v1.10.0
full diff: https://github.com/containerd/containerd/compare/api/v1.9.0...api/v1.10.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 16:12:24 +01:00
Sebastiaan van Stijn
1da018e2e7 Merge pull request #51461 from tonistiigi/update-swarmkit-etcd-3.6.5
vendor: github.com/moby/swarmkit/v2 v2.1.2-0.20251110192100-17b8d222e7dd
2025-11-11 16:11:22 +01:00
Sebastiaan van Stijn
81d9fdb838 Merge pull request #51477 from thaJeztah/discovery_enum
libnetwork/discoverapi: use DiscoveryType for enum
2025-11-11 15:01:25 +01:00
Sebastiaan van Stijn
b74e6fefba Merge pull request #51469 from thaJeztah/daemon_rm_deadcode
remove some dead code
2025-11-11 15:01:02 +01:00
Tonis Tiigi
0a494a7303 vendor: github.com/moby/swarmkit/v2 v2.1.2-0.20251110192100-17b8d222e7dd
Brings in etcd update to v3.6.5

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 14:58:36 +01:00
Sebastiaan van Stijn
bba948f4ab Merge pull request #51479 from thaJeztah/bump_hcsshim
vendor: github.com/Microsoft/hcsshim v0.14.0-rc.1
2025-11-11 14:43:58 +01:00
Sebastiaan van Stijn
74d4e5b382 Merge pull request #51478 from thaJeztah/bump_otel_grpc
vendor: go.opentelemetry.io/otel v1.38.0, google.golang.org/grpc v1.76.0, google.golang.org/protobuf v1.36.10
2025-11-11 14:43:34 +01:00
Sebastiaan van Stijn
ede54ceb49 Merge pull request #51271 from austinvazquez/update-containerd-binary-2.2.0
Dockerfile: update containerd binary to v2.2.0 (static binaries and CI only)
2025-11-11 14:22:12 +01:00
Sebastiaan van Stijn
a040664176 vendor: github.com/Microsoft/hcsshim v0.14.0-rc.1
full diff: https://github.com/Microsoft/hcsshim/compare/v0.13.0...v0.14.0-rc.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 13:22:25 +01:00
Sebastiaan van Stijn
e65995d896 vendor: google.golang.org/grpc v1.76.0, google.golang.org/protobuf v1.36.10
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 13:12:09 +01:00
Sebastiaan van Stijn
65bb1bb21f vendor: go.opentelemetry.io/otel v1.38.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 13:12:08 +01:00
Sebastiaan van Stijn
4b2e073bdb Merge pull request #51473 from thaJeztah/bump_compress
vendor: github.com/klauspost/compress v1.18.1
2025-11-11 13:11:50 +01:00
Sebastiaan van Stijn
6b8285f965 libnetwork/discoverapi: use DiscoveryType for enum
This type describes the options defined as consts below it, so make
those consts typed. While updating, I also removed the use of iota
to prevent accidentally changing their values (and if this API is
implemented elsewhere)

(but mostly because I'm not a fan of iota ':))

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 13:10:17 +01:00
Sebastiaan van Stijn
b66e501cad Merge pull request #51474 from thaJeztah/bump_google_logging
vendor: cloud.google.com/go/logging v1.13.0
2025-11-11 12:55:58 +01:00
Sebastiaan van Stijn
9e86f904d7 Merge pull request #51472 from thaJeztah/bump_prometheus
vendor: golang.org/x/oauth2 v0.30.0, github.com/prometheus/client_golang v1.23.2
2025-11-11 12:31:20 +01:00
Sebastiaan van Stijn
409d0cf4fc Merge pull request #51470 from thaJeztah/bump_cgroups
vendor: github.com/containerd/cgroups/v3 v3.1.0
2025-11-11 12:30:18 +01:00
Sebastiaan van Stijn
602da551ce Merge pull request #51471 from thaJeztah/bump_tarsplit
vendor: github.com/vbatts/tar-split v0.12.2
2025-11-11 12:29:55 +01:00
Sebastiaan van Stijn
7eaf25ee59 vendor: cloud.google.com/go/logging v1.13.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 11:20:47 +01:00
Sebastiaan van Stijn
3f6f3b9ed2 vendor: cloud.google.com/go/compute/metadata v0.8.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 11:20:47 +01:00
Sebastiaan van Stijn
94eb87f4c0 vendor: github.com/klauspost/compress v1.18.1
full diff: https://github.com/klauspost/compress/compare/v1.18.0...v1.18.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 11:18:07 +01:00
Sebastiaan van Stijn
1621c4e7ad vendor: github.com/prometheus/client_golang v1.23.2
full diff: https://github.com/prometheus/client_golang/compare/v1.22.0...v1.23.2

Also updating to go.yaml.in/yaml/v2 v2.4.3

- Retract v2 tags that cannot be installed

full diff: https://github.com/yaml/go-yaml/compare/v2.4.2...v2.4.3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 10:33:47 +01:00
Sebastiaan van Stijn
efc3e93b1e vendor: golang.org/x/oauth2 v0.30.0
full diff: https://github.com/golang/oauth2/compare/v0.29.0...v0.30.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 10:33:46 +01:00
Sebastiaan van Stijn
e773a0cf50 vendor: github.com/vbatts/tar-split v0.12.2
- archive/tar: set a limit on the size of GNU sparse file 1.0 regions
- fixes CVE-2025-58183

full diff: https://github.com/vbatts/tar-split/compare/v0.12.1...v0.12.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 10:07:19 +01:00
Sebastiaan van Stijn
ebcf9bb0c4 vendor: github.com/containerd/cgroups/v3 v3.1.0
Notable changes:

- support network metrics for cgroupv2
- cgroupv2: simplify parseCgroupFile
- introduce cpu burst stat
- add cgroupV2 CPUQuotaPeriodUSec support
    - addresses: cgroup v2 does not set CPUQuotaPeriodUSec
- read cpu.stat regardless if controller enabled
    - addresses: support cgroup v2 CPU stats when controller not enabled
- add cgroup2 hugetlb failcnt metric
- cgroup2: should add IN_CLOEXEC for inotify fd
- cgroup2: cpu shares: follow the behavior of runc v1.3.2
- cgroup2: fix event loss and resource issues in EventChan

full diff: https://github.com/containerd/cgroups/compare/v3.0.5...v3.1.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 09:53:02 +01:00
Sebastiaan van Stijn
7c29edf1b4 hack: remove cpexp package
This looks to have been accidentally committed in
1b1608f2cd

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 09:37:43 +01:00
Sebastiaan van Stijn
bb56c4d7e7 daemon: remove some deprecated and unused code
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-11 09:05:41 +01:00
Austin Vazquez
365e588d0f Dockerfile: update containerd binary to v2.2.0 (static binaries and CI only)
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
2025-11-10 21:06:12 -05:00
Austin Vazquez
d166f42ef2 Merge pull request #51467 from thaJeztah/client_test_context
client: use t.Context in tests
2025-11-10 20:00:12 -06:00
Sebastiaan van Stijn
c950796596 client: use t.Context in tests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-11-10 23:23:23 +01:00
1533 changed files with 159526 additions and 29869 deletions

View File

@@ -6,9 +6,18 @@ runs:
steps:
- run: |
set -e
# Jaeger is set up on Windows through an inline run step. If you update Jaeger here, don't forget to update
# the version set in .github/workflows/.windows.yml.
docker run -d --net=host --name jaeger -e COLLECTOR_OTLP_ENABLED=true jaegertracing/all-in-one:1.46
# The OTEL Collector is set up on Windows through an inline run step. If
# you update the collector here, don't forget to update the version set
# in .github/workflows/.windows.yml.
mkdir -p /tmp/reports
chmod 777 /tmp/reports
docker run -d --net=host --name otelcol \
-v "$(pwd)/otelcol-ci-config.yml:/etc/otelcol-contrib/config.yaml" \
-v "/tmp/reports:/data" \
otel/opentelemetry-collector-contrib:0.140.0 \
--config file:/etc/otelcol-contrib/config.yaml \
--config "yaml:exporters::file::path: /data/otel-trace.jsonl"
docker0_ip="$(ip -f inet addr show docker0 | grep -Po 'inet \K[\d.]+')"
echo "OTEL_EXPORTER_OTLP_ENDPOINT=http://${docker0_ip}:4318" >> "${GITHUB_ENV}"
echo "OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf" >> "${GITHUB_ENV}"
shell: bash

View File

@@ -25,7 +25,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
-

View File

@@ -16,7 +16,7 @@ on:
workflow_call:
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
GOTESTLIST_VERSION: v0.3.1
TESTSTAT_VERSION: v0.1.25
SETUP_BUILDX_VERSION: edge
@@ -36,7 +36,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -87,7 +87,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-unit--${{ matrix.mode }}
path: /tmp/reports/*
@@ -103,13 +103,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
pattern: test-reports-unit-*
path: /tmp/reports

View File

@@ -21,7 +21,7 @@ on:
default: "graphdriver"
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
GOTESTLIST_VERSION: v0.3.1
TESTSTAT_VERSION: v0.1.25
ITG_CLI_MATRIX_SIZE: 6
@@ -39,7 +39,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -68,13 +68,12 @@ jobs:
name: Prepare reports
if: always()
run: |
docker stop otelcol
mkdir -p bundles /tmp/reports
find bundles -path '*/root/*overlay2' -prune -o -type f \( -name '*-report.json' -o -name '*.log' -o -name '*.out' -o -name '*.prof' -o -name '*-report.xml' \) -print | xargs sudo tar -czf /tmp/reports.tar.gz
tar -xzf /tmp/reports.tar.gz -C /tmp/reports
sudo chown -R $(id -u):$(id -g) /tmp/reports
tree -nh /tmp/reports
curl -sSLf localhost:16686/api/traces?service=integration-test-client > /tmp/reports/jaeger-trace.json
-
name: Test daemon logs
if: always()
@@ -83,7 +82,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-docker-py-${{ inputs.storage }}
path: /tmp/reports/*
@@ -96,7 +95,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -170,7 +169,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -228,13 +227,13 @@ jobs:
reportsPath="/tmp/reports/$reportsName"
echo "TESTREPORTS_NAME=$reportsName" >> $GITHUB_ENV
docker stop otelcol
mkdir -p bundles $reportsPath
find bundles -path '*/root/*overlay2' -prune -o -type f \( -name '*-report.json' -o -name '*.log' -o -name '*.out' -o -name '*.prof' -o -name '*-report.xml' \) -print | xargs sudo tar -czf /tmp/reports.tar.gz
tar -xzf /tmp/reports.tar.gz -C $reportsPath
mv /tmp/reports/otel-trace*.jsonl $reportsPath/
sudo chown -R $(id -u):$(id -g) $reportsPath
tree -nh $reportsPath
curl -sSLf localhost:16686/api/traces?service=integration-test-client > $reportsPath/jaeger-trace.json
-
name: Send to Codecov
uses: codecov/codecov-action@v4
@@ -251,7 +250,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-integration-${{ inputs.storage }}-${{ env.TESTREPORTS_NAME }}
path: /tmp/reports/*
@@ -267,13 +266,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
path: /tmp/reports
pattern: test-reports-integration-${{ inputs.storage }}-*
@@ -296,10 +295,10 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
@@ -394,7 +393,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -443,14 +442,14 @@ jobs:
reportsPath=/tmp/reports/$reportsName
echo "TESTREPORTS_NAME=$reportsName" >> $GITHUB_ENV
docker stop otelcol
mkdir -p bundles $reportsPath
echo "${{ matrix.test }}" | tr -s '|' '\n' | tee -a "$reportsPath/tests.txt"
find bundles -path '*/root/*overlay2' -prune -o -type f \( -name '*-report.json' -o -name '*.log' -o -name '*.out' -o -name '*.prof' -o -name '*-report.xml' \) -print | xargs sudo tar -czf /tmp/reports.tar.gz
tar -xzf /tmp/reports.tar.gz -C $reportsPath
mv /tmp/reports/otel-trace*.jsonl $reportsPath/
sudo chown -R $(id -u):$(id -g) $reportsPath
tree -nh $reportsPath
curl -sSLf localhost:16686/api/traces?service=integration-test-client > $reportsPath/jaeger-trace.json
-
name: Send to Codecov
uses: codecov/codecov-action@v4
@@ -467,7 +466,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-integration-cli-${{ inputs.storage }}-${{ matrix.mode }}-${{ env.TESTREPORTS_NAME }}
path: /tmp/reports/*
@@ -483,13 +482,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
path: /tmp/reports
pattern: test-reports-integration-cli-${{ inputs.storage }}-${{ matrix.mode }}-*

View File

@@ -20,7 +20,7 @@ on:
type: string
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
TESTSTAT_VERSION: v0.1.25
jobs:
@@ -39,7 +39,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up Lima
uses: lima-vm/lima-actions/setup@03b96d61959e83b2c737e44162c3088e81de0886 # v1.0.1
@@ -167,7 +167,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-integration-${{ env.TESTREPORTS_NAME }}
path: /tmp/reports/*
@@ -183,7 +183,7 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
@@ -192,7 +192,7 @@ jobs:
run: echo "TESTREPORTS_NAME=$(basename ${{ inputs.template }})*" >> $GITHUB_ENV
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
path: /tmp/reports
pattern: test-reports-integration-${{ env.TESTREPORTS_NAME }}

View File

@@ -28,7 +28,7 @@ on:
default: false
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
GOTESTLIST_VERSION: v0.3.1
TESTSTAT_VERSION: v0.1.25
WINDOWS_BASE_IMAGE: mcr.microsoft.com/windows/servercore
@@ -53,7 +53,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: ${{ env.GOPATH }}/src/github.com/docker/docker
-
@@ -98,7 +98,7 @@ jobs:
docker cp "${{ env.TEST_CTN_NAME }}`:c`:\containerd\bin\containerd-shim-runhcs-v1.exe" ${{ env.BIN_OUT }}\
-
name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: build-${{ inputs.storage }}-${{ inputs.os }}
path: ${{ env.BIN_OUT }}/*
@@ -117,7 +117,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: ${{ env.GOPATH }}/src/github.com/docker/docker
-
@@ -166,7 +166,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: ${{ inputs.os }}-${{ inputs.storage }}-unit-reports
path: ${{ env.GOPATH }}\src\github.com\docker\docker\bundles\*
@@ -181,13 +181,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
name: ${{ inputs.os }}-${{ inputs.storage }}-unit-reports
path: /tmp/artifacts
@@ -208,10 +208,10 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
@@ -264,24 +264,27 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: ${{ env.GOPATH }}/src/github.com/docker/docker
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Set up Jaeger
name: Set up OpenTelemetry Collector
run: |
# Jaeger is set up on Linux through the setup-tracing action. If you update Jaeger here, don't forget to
# The collectors is set up on Linux through the setup-tracing action. If you update the collector here, don't forget to
# update the version set in .github/actions/setup-tracing/action.yml.
Invoke-WebRequest -Uri "https://github.com/jaegertracing/jaeger/releases/download/v1.46.0/jaeger-1.46.0-windows-amd64.tar.gz" -OutFile ".\jaeger-1.46.0-windows-amd64.tar.gz"
tar -zxvf ".\jaeger-1.46.0-windows-amd64.tar.gz"
Start-Process '.\jaeger-1.46.0-windows-amd64\jaeger-all-in-one.exe'
echo "OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
New-Item -ItemType Directory -Force -Path bundles -ErrorAction Continue
Start-Process "msiexec" -ArgumentList "/i",
"https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.140.0/otelcol_0.140.0_windows_x64.msi",
"/qn", "/l*v", "$(Join-Path (Get-Location) "bundles/otelcol-install.log")",
"COLLECTOR_SVC_ARGS=`"--config=`"`"file:$(Join-Path (Get-Location) "otelcol-ci-config.yml")`"`" --config=`"`"yaml:exporters::file::path: $(Join-Path (Get-Location) "bundles/otel-trace.jsonl")`"`"`"" `
-NoNewWindow -Wait
@("OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318", "OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf") | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
shell: pwsh
-
name: Env
@@ -289,14 +292,14 @@ jobs:
Get-ChildItem Env: | Out-String
-
name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
name: build-${{ inputs.storage }}-${{ inputs.os }}
path: ${{ env.BIN_OUT }}
-
name: Init
run: |
New-Item -ItemType "directory" -Path "bundles"
New-Item -ItemType "directory" -Path "bundles" -ErrorAction SilentlyContinue
If ("${{ inputs.os }}" -eq "windows-2025") {
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2025 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
} ElseIf ("${{ inputs.os }}" -eq "windows-2022") {
@@ -342,14 +345,14 @@ jobs:
"--exec-root=$env:TEMP\moby-exec", `
"--pidfile=$env:TEMP\docker.pid", `
"--register-service"
# Make the env-var visible to the service-managed dockerd, as there's no CLI flag for this option.
$dockerEnviron = @("DOCKER_MIN_API_VERSION=1.24")
$dockerEnviron += @(Get-Item Env:\OTEL_* | ForEach-Object { "$($_.Name)=$($_.Value)" })
If ("${{ inputs.storage }}" -eq "graphdriver") {
# Make the env-var visible to the service-managed dockerd, as there's no CLI flag for this option.
& reg add "HKLM\SYSTEM\CurrentControlSet\Services\docker" /v Environment /t REG_MULTI_SZ /s '@' /d "DOCKER_MIN_API_VERSION=1.24@TEST_INTEGRATION_USE_GRAPHDRIVER=1"
$dockerEnviron += @("TEST_INTEGRATION_USE_GRAPHDRIVER=1")
echo "TEST_INTEGRATION_USE_GRAPHDRIVER=1" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
} Else {
# Make the env-var visible to the service-managed dockerd, as there's no CLI flag for this option.
& reg add "HKLM\SYSTEM\CurrentControlSet\Services\docker" /v Environment /t REG_MULTI_SZ /s '@' /d DOCKER_MIN_API_VERSION=1.24
}
New-ItemProperty -Name "Environment" -Path "HKLM:\SYSTEM\CurrentControlSet\Services\docker" -PropertyType MultiString -Value $dockerEnviron
Write-Host "Starting service"
Start-Service -Name docker
Write-Host "Service started successfully!"
@@ -459,16 +462,14 @@ jobs:
ForEach-Object {"$($_.TimeCreated.ToUniversalTime().ToString("o")) [$($_.LevelDisplayName)] $($_.Message)"} |
Tee-Object -file ".\bundles\daemon.log"
-
name: Download Jaeger traces
name: Stop OpenTelemetry Collector
if: always()
run: |
Invoke-WebRequest `
-Uri "http://127.0.0.1:16686/api/traces?service=integration-test-client" `
-OutFile ".\bundles\jaeger-trace.json"
(Stop-Service -DisplayName "OpenTelemetry Collector" -PassThru).WaitForStatus('Stopped', (New-TimeSpan -Seconds 30))
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: ${{ inputs.os }}-${{ inputs.storage }}-integration-reports-${{ matrix.runtime }}-${{ env.TESTREPORTS_NAME }}
path: ${{ env.GOPATH }}\src\github.com\docker\docker\bundles\*
@@ -495,13 +496,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
path: /tmp/reports
pattern: ${{ inputs.os }}-${{ inputs.storage }}-integration-reports-${{ matrix.runtime }}-*

View File

@@ -23,7 +23,7 @@ on:
pull_request:
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
TESTSTAT_VERSION: v0.1.25
DESTDIR: ./build
SETUP_BUILDX_VERSION: edge
@@ -101,7 +101,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -146,7 +146,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-unit-arm64-graphdriver
path: /tmp/reports/*
@@ -162,13 +162,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
pattern: test-reports-unit-arm64-*
path: /tmp/reports
@@ -191,7 +191,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Set up runner
uses: ./.github/actions/setup-runner
@@ -226,13 +226,14 @@ jobs:
name: Prepare reports
if: always()
run: |
docker stop otelcol
reportsPath="/tmp/reports/arm64-graphdriver"
mkdir -p bundles $reportsPath
find bundles -path '*/root/*overlay2' -prune -o -type f \( -name '*-report.json' -o -name '*.log' -o -name '*.out' -o -name '*.prof' -o -name '*-report.xml' \) -print | xargs sudo tar -czf /tmp/reports.tar.gz
tar -xzf /tmp/reports.tar.gz -C $reportsPath
mv /tmp/reports/otel-trace*.jsonl $reportsPath/
sudo chown -R $(id -u):$(id -g) $reportsPath
tree -nh $reportsPath
curl -sSLf localhost:16686/api/traces?service=integration-test-client > $reportsPath/jaeger-trace.json
-
name: Send to Codecov
uses: codecov/codecov-action@v4
@@ -249,7 +250,7 @@ jobs:
-
name: Upload reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-integration-arm64-graphdriver
path: /tmp/reports/*
@@ -265,13 +266,13 @@ jobs:
steps:
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
-
name: Download reports
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
path: /tmp/reports
pattern: test-reports-integration-arm64-*

View File

@@ -49,7 +49,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Docker meta
id: meta
@@ -83,7 +83,7 @@ jobs:
mv "${bakeFile#cwd://}" "/tmp/bake-meta.json"
-
name: Upload meta bake definition
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: bake-meta
path: /tmp/bake-meta.json
@@ -109,7 +109,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
-
@@ -119,7 +119,7 @@ jobs:
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
-
name: Download meta bake definition
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
name: bake-meta
path: /tmp
@@ -164,7 +164,7 @@ jobs:
-
name: Upload digest
if: github.event_name != 'pull_request' && github.repository == 'moby/moby'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
@@ -180,13 +180,13 @@ jobs:
steps:
-
name: Download meta bake definition
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
name: bake-meta
path: /tmp
-
name: Download digests
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
path: /tmp/digests
pattern: digests-*

View File

@@ -23,7 +23,7 @@ on:
pull_request:
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
DESTDIR: ./build
SETUP_BUILDX_VERSION: edge
SETUP_BUILDKIT_IMAGE: moby/buildkit:latest
@@ -53,7 +53,7 @@ jobs:
targets: binary
-
name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: binary
path: ${{ env.DESTDIR }}
@@ -100,12 +100,12 @@ jobs:
uses: crazy-max/ghaction-github-runtime@v3
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: moby
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
@@ -116,7 +116,7 @@ jobs:
working-directory: moby
-
name: Checkout BuildKit ${{ env.BUILDKIT_REF }}
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
repository: ${{ env.BUILDKIT_REPO }}
ref: ${{ env.BUILDKIT_REF }}
@@ -133,7 +133,7 @@ jobs:
buildkitd-flags: --debug
-
name: Download binary artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
name: binary
path: ./buildkit/build/moby/
@@ -184,7 +184,7 @@ jobs:
working-directory: ${{ env.GOPATH }}/src/github.com/docker/docker
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: ${{ env.GOPATH }}/src/github.com/docker/docker
@@ -199,7 +199,7 @@ jobs:
echo "WINDOWS_BASE_IMAGE_TAG=${{ env.WINDOWS_BASE_TAG_2022 }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
@@ -225,7 +225,7 @@ jobs:
go install github.com/distribution/distribution/v3/cmd/registry@latest
- name: Checkout BuildKit
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
repository: moby/buildkit
ref: master
@@ -248,7 +248,7 @@ jobs:
cp ${{ env.GOPATH }}\bin\buildctl.exe ${{ env.BIN_OUT }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: build-windows
path: ${{ env.BIN_OUT }}/*
@@ -307,12 +307,12 @@ jobs:
uses: crazy-max/ghaction-github-runtime@v3
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: moby
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false
@@ -324,14 +324,14 @@ jobs:
working-directory: moby
- name: Checkout BuildKit ${{ env.BUILDKIT_REF }}
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
repository: ${{ env.BUILDKIT_REPO }}
ref: ${{ env.BUILDKIT_REF }}
path: buildkit
- name: Download Moby artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v6
with:
name: build-windows
path: ${{ env.BIN_OUT }}

View File

@@ -75,7 +75,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Create matrix
id: platforms

View File

@@ -34,7 +34,7 @@ on:
- cron: '0 9 * * 4'
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
jobs:
codeql:
@@ -47,11 +47,11 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 2
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: false

View File

@@ -23,7 +23,7 @@ on:
pull_request:
env:
GO_VERSION: "1.25.4"
GO_VERSION: "1.25.5"
GIT_PAGER: "cat"
PAGER: "cat"
SETUP_BUILDX_VERSION: edge
@@ -67,7 +67,14 @@ jobs:
set: |
*.cache-from=type=gha,scope=dev${{ matrix.mode }}
*.cache-to=type=gha,scope=dev${{ matrix.mode }}
*.output=type=cacheonly
${{ matrix.mode == '' && '*.output=type=docker,dest=/tmp/dev-image.tar' || '*.output=type=cacheonly' }}
-
name: Cache dev image
if: matrix.mode == ''
uses: actions/cache/save@v4
with:
key: dev-image-${{ github.run_id }}
path: /tmp/dev-image.tar
test:
if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'ci/validate-only') }}
@@ -103,7 +110,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Create matrix
id: scripts
@@ -122,13 +129,12 @@ jobs:
- validate-prepare
- build-dev
strategy:
fail-fast: true
matrix:
script: ${{ fromJson(needs.validate-prepare.outputs.matrix) }}
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
-
@@ -139,15 +145,19 @@ jobs:
uses: docker/setup-buildx-action@v3
with:
version: ${{ env.SETUP_BUILDX_VERSION }}
driver-opts: image=${{ env.SETUP_BUILDKIT_IMAGE }}
driver: docker
buildkitd-flags: --debug
-
name: Build dev image
uses: docker/bake-action@v6
name: Restore dev image
uses: actions/cache/restore@v4
with:
targets: dev
set: |
dev.cache-from=type=gha,scope=dev
key: dev-image-${{ github.run_id }}
path: /tmp/dev-image.tar
fail-on-cache-miss: true
-
name: Load dev image
run: |
docker load -i /tmp/dev-image.tar
-
name: Validate
run: |
@@ -164,7 +174,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
-
name: Create matrix
id: platforms

View File

@@ -81,13 +81,24 @@ jobs:
- name: Check release branch
id: title_branch
run: |
# If PR targets a different branch than master, the PR title should mention the target branch in square brackets, for example:
# [27.1 backport] Some change that needs backporting to 27.1
# [27.1] Change directly targeting the 27.1 branch
# [docker-29.x] Change directly targeting the docker-29.x branch
# [docker-29.x backport] Some change that needs backporting to docker-29.x
# get the intended major version prefix ("[27.1 backport]" -> "27.") from the PR title.
[[ "$PR_TITLE" =~ ^\[([0-9]*\.)[^]]*\] ]] && branch="${BASH_REMATCH[1]}"
target_branch=$(echo "$PR_TITLE" | sed -nE 's/^\[([^]]+)\].*/\1/p' | sed 's/ backport$//')
# get major version prefix from the release branch ("27.x -> "27.")
[[ "$GITHUB_BASE_REF" =~ ^([0-9]*\.) ]] && target_branch="${BASH_REMATCH[1]}" || target_branch="$GITHUB_BASE_REF"
echo "target_branch: $target_branch"
echo "GITHUB_BASE_REF: $GITHUB_BASE_REF"
if [[ "$target_branch" != "$branch" ]] && ! [[ "$GITHUB_BASE_REF" == "master" && "$branch" == "" ]]; then
# If the PR is opened against the master branch and the target branch is not specified, exit early.
if [[ "$GITHUB_BASE_REF" == "master" && "$target_branch" == "" ]]; then
exit 0
fi
if [[ "$target_branch" != "$GITHUB_BASE_REF" ]]; then
echo "::error::PR is opened against the $GITHUB_BASE_REF branch, but its title suggests otherwise."
exit 1
fi

View File

@@ -3,7 +3,7 @@ version: "2"
run:
# prevent golangci-lint from deducting the go version to lint for through go.mod,
# which causes it to fallback to go1.17 semantics.
go: "1.25.4"
go: "1.25.5"
concurrency: 2
# Only supported with go modules enabled (build flag -mod=vendor only valid when using modules)
# modules-download-mode: vendor

View File

@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.25.4
ARG GO_VERSION=1.25.5
ARG BASE_DEBIAN_DISTRO="bookworm"
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"
@@ -13,7 +13,7 @@ ARG XX_VERSION=1.7.0
ARG VPNKIT_VERSION=0.6.0
# DOCKERCLI_VERSION is the version of the CLI to install in the dev-container.
ARG DOCKERCLI_VERSION=v28.5.0
ARG DOCKERCLI_VERSION=v29.0.1
ARG DOCKERCLI_REPOSITORY="https://github.com/docker/cli.git"
# cli version used for integration-cli tests
@@ -21,7 +21,7 @@ ARG DOCKERCLI_INTEGRATION_REPOSITORY="https://github.com/docker/cli.git"
ARG DOCKERCLI_INTEGRATION_VERSION=v25.0.5
# BUILDX_VERSION is the version of buildx to install in the dev container.
ARG BUILDX_VERSION=0.29.1
ARG BUILDX_VERSION=0.30.1
# COMPOSE_VERSION is the version of compose to install in the dev container.
ARG COMPOSE_VERSION=v2.40.0
@@ -163,7 +163,7 @@ RUN git init . && git remote add origin "https://github.com/containerd/container
# integration tests. The distributed docker .deb and .rpm packages depend on a
# separate (containerd.io) package, which may be a different version as is
# specified here.
ARG CONTAINERD_VERSION=v2.1.5
ARG CONTAINERD_VERSION=v2.2.0
RUN git fetch -q --depth 1 origin "${CONTAINERD_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
FROM base AS containerd-build
@@ -254,7 +254,7 @@ RUN git init . && git remote add origin "https://github.com/opencontainers/runc.
# This version should usually match the version that is used by the containerd version
# that is used. If you need to update runc, open a pull request in the containerd
# project first, and update both after that is merged.
ARG RUNC_VERSION=v1.3.3
ARG RUNC_VERSION=v1.3.4
RUN git fetch -q --depth 1 origin "${RUNC_VERSION}" +refs/tags/*:refs/tags/* && git checkout -q FETCH_HEAD
FROM base AS runc-build

View File

@@ -5,7 +5,7 @@
# This represents the bare minimum required to build and test Docker.
ARG GO_VERSION=1.25.4
ARG GO_VERSION=1.25.5
ARG BASE_DEBIAN_DISTRO="bookworm"
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"

View File

@@ -161,7 +161,7 @@ FROM ${WINDOWS_BASE_IMAGE}:${WINDOWS_BASE_IMAGE_TAG}
# Use PowerShell as the default shell
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
ARG GO_VERSION=1.25.4
ARG GO_VERSION=1.25.5
# GOTESTSUM_VERSION is the version of gotest.tools/gotestsum to install.
ARG GOTESTSUM_VERSION=v1.13.0

View File

@@ -23,19 +23,28 @@ import (
)
func main() {
apiClient, err := client.New(client.FromEnv, client.WithAPIVersionNegotiation())
// Create a new client that handles common environment variables
// for configuration (DOCKER_HOST, DOCKER_API_VERSION), and does
// API-version negotiation to allow downgrading the API version
// when connecting with an older daemon version.
apiClient, err := client.New(client.FromEnv)
if err != nil {
panic(err)
}
defer apiClient.Close()
containers, err := apiClient.ContainerList(context.Background(), client.ContainerListOptions{All: true})
// List all containers (both stopped and running).
result, err := apiClient.ContainerList(context.Background(), client.ContainerListOptions{
All: true,
})
if err != nil {
panic(err)
}
for _, ctr := range containers {
fmt.Printf("%s %s (status: %s)\n", ctr.ID, ctr.Image, ctr.Status)
// Print each container's ID, status and the image it was created from.
fmt.Printf("%s %-22s %s\n", "ID", "STATUS", "IMAGE")
for _, ctr := range result.Items {
fmt.Printf("%s %-22s %s\n", ctr.ID, ctr.Status, ctr.Image)
}
}
```

View File

@@ -8,10 +8,8 @@ https://docs.docker.com/reference/api/engine/
You use the library by constructing a client object using [New]
and calling methods on it. The client can be configured from environment
variables by passing the [FromEnv] option, and the [WithAPIVersionNegotiation]
option to allow downgrading the API version used when connecting with an older
daemon version. Other options cen be configured manually by passing any of
the available [Opt] options.
variables by passing the [FromEnv] option. Other options can be configured
manually by passing any of the available [Opt] options.
For example, to list running containers (the equivalent of "docker ps"):
@@ -30,7 +28,7 @@ For example, to list running containers (the equivalent of "docker ps"):
// for configuration (DOCKER_HOST, DOCKER_API_VERSION), and does
// API-version negotiation to allow downgrading the API version
// when connecting with an older daemon version.
apiClient, err := client.New(client.FromEnv, client.WithAPIVersionNegotiation())
apiClient, err := client.New(client.FromEnv)
if err != nil {
log.Fatal(err)
}
@@ -103,18 +101,16 @@ import (
const DummyHost = "api.moby.localhost"
// MaxAPIVersion is the highest REST API version supported by the client.
// If API-version negotiation is enabled (see [WithAPIVersionNegotiation],
// [Client.NegotiateAPIVersion]), the client may downgrade its API version.
// Similarly, the [WithVersion] and [WithVersionFromEnv] allow overriding
// the version.
// If API-version negotiation is enabled, the client may downgrade its API version.
// Similarly, the [WithAPIVersion] and [WithAPIVersionFromEnv] options allow
// overriding the version and disable API-version negotiation.
//
// This version may be lower than the version of the api library module used.
const MaxAPIVersion = "1.52"
// fallbackAPIVersion is the version to fall back to if API-version negotiation
// fails. API versions below this version are not supported by the client,
// and not considered when negotiating.
const fallbackAPIVersion = "1.44"
// MinAPIVersion is the minimum API version supported by the client. API versions
// below this version are not considered when performing API-version negotiation.
const MinAPIVersion = "1.44"
// Ensure that Client always implements APIClient.
var _ APIClient = &Client{}
@@ -174,8 +170,13 @@ func NewClientWithOpts(ops ...Opt) (*Client, error) {
// It takes an optional list of [Opt] functional arguments, which are applied in
// the order they're provided, which allows modifying the defaults when creating
// the client. For example, the following initializes a client that configures
// itself with values from environment variables ([FromEnv]), and has automatic
// API version negotiation enabled ([WithAPIVersionNegotiation]).
// itself with values from environment variables ([FromEnv]).
//
// By default, the client automatically negotiates the API version to use when
// making requests. API version negotiation is performed on the first request;
// subsequent requests do not re-negotiate. Use [WithAPIVersion] or
// [WithAPIVersionFromEnv] to configure the client with a fixed API version
// and disable API version negotiation.
//
// cli, err := client.New(
// client.FromEnv,
@@ -213,6 +214,12 @@ func New(ops ...Opt) (*Client, error) {
}
}
if cfg.envAPIVersion != "" {
c.setAPIVersion(cfg.envAPIVersion)
} else if cfg.manualAPIVersion != "" {
c.setAPIVersion(cfg.manualAPIVersion)
}
if tr, ok := c.client.Transport.(*http.Transport); ok {
// Store the base transport before we wrap it in tracing libs below
// This is used, as an example, to close idle connections when the client is closed
@@ -278,7 +285,7 @@ func (cli *Client) Close() error {
// be negotiated when making the actual requests, and for which cases
// we cannot do the negotiation lazily.
func (cli *Client) checkVersion(ctx context.Context) error {
if cli.manualOverride || !cli.negotiateVersion || cli.negotiated.Load() {
if cli.negotiated.Load() {
return nil
}
_, err := cli.Ping(ctx, PingOptions{
@@ -306,36 +313,47 @@ func (cli *Client) ClientVersion() string {
}
// negotiateAPIVersion updates the version to match the API version from
// the ping response. It falls back to the lowest version supported if the
// API version is empty, or returns an error if the API version is lower than
// the lowest supported API version, in which case the version is not modified.
// the ping response.
//
// It returns an error if version is invalid, or lower than the minimum
// supported API version in which case the client's API version is not
// updated, and negotiation is not marked as completed.
func (cli *Client) negotiateAPIVersion(pingVersion string) error {
pingVersion = strings.TrimPrefix(pingVersion, "v")
if pingVersion == "" {
// TODO(thaJeztah): consider returning an error on empty value or not falling back; see https://github.com/moby/moby/pull/51119#discussion_r2413148487
pingVersion = fallbackAPIVersion
} else if versions.LessThan(pingVersion, fallbackAPIVersion) {
return cerrdefs.ErrInvalidArgument.WithMessage(fmt.Sprintf("API version %s is not supported by this client: the minimum supported API version is %s", pingVersion, fallbackAPIVersion))
var err error
pingVersion, err = parseAPIVersion(pingVersion)
if err != nil {
return err
}
if versions.LessThan(pingVersion, MinAPIVersion) {
return cerrdefs.ErrInvalidArgument.WithMessage(fmt.Sprintf("API version %s is not supported by this client: the minimum supported API version is %s", pingVersion, MinAPIVersion))
}
// if the client is not initialized with a version, start with the latest supported version
if cli.version == "" {
cli.version = MaxAPIVersion
negotiatedVersion := cli.version
if negotiatedVersion == "" {
negotiatedVersion = MaxAPIVersion
}
// if server version is lower than the client version, downgrade
if versions.LessThan(pingVersion, cli.version) {
cli.version = pingVersion
if versions.LessThan(pingVersion, negotiatedVersion) {
negotiatedVersion = pingVersion
}
// Store the results, so that automatic API version negotiation (if enabled)
// won't be performed on the next request.
if cli.negotiateVersion {
cli.negotiated.Store(true)
}
cli.setAPIVersion(negotiatedVersion)
return nil
}
// setAPIVersion sets the client's API version and marks API version negotiation
// as completed, so that automatic API version negotiation (if enabled) won't
// be performed on the next request.
func (cli *Client) setAPIVersion(version string) {
cli.version = version
cli.negotiated.Store(true)
}
// DaemonHost returns the host address used by the client
func (cli *Client) DaemonHost() string {
return cli.host

View File

@@ -13,10 +13,11 @@ func ExampleNew() {
// for configuration (DOCKER_HOST, DOCKER_API_VERSION), and does
// API-version negotiation to allow downgrading the API version
// when connecting with an older daemon version.
apiClient, err := client.New(client.FromEnv, client.WithAPIVersionNegotiation())
apiClient, err := client.New(client.FromEnv)
if err != nil {
log.Fatal(err)
}
defer apiClient.Close()
// List all containers (both stopped and running).
result, err := apiClient.ContainerList(context.Background(), client.ContainerListOptions{

View File

@@ -5,9 +5,13 @@ import (
"fmt"
"io"
"net/http"
"runtime"
"strconv"
"strings"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/common"
"github.com/moby/moby/api/types/swarm"
)
// defaultAPIPath is the API path prefix for the default API version used.
@@ -40,20 +44,88 @@ func assertRequestWithQuery(req *http.Request, expMethod string, expectedPath st
return nil
}
// ensureBody makes sure the response has a Body, using [http.NoBody] if
// none is present, and returns it as a testRoundTripper.
// ensureBody makes sure the response has a Body (using [http.NoBody] if
// none is present), and that the request is set on the response, then returns
// it as a testRoundTripper.
func ensureBody(f func(req *http.Request) (*http.Response, error)) testRoundTripper {
return func(req *http.Request) (*http.Response, error) {
resp, err := f(req)
if resp != nil && resp.Body == nil {
resp.Body = http.NoBody
if resp != nil {
if resp.Body == nil {
resp.Body = http.NoBody
}
if resp.Request == nil {
resp.Request = req
}
}
return resp, err
}
}
// WithMockClient is a test helper that allows you to inject a mock client for testing.
// makeTestRoundTripper makes sure the response has a Body, using [http.NoBody] if
// none is present, and returns it as a testRoundTripper. If withDefaults is set,
// it also mocks the "/_ping" endpoint and sets default headers as returned
// by the daemon.
func makeTestRoundTripper(f func(req *http.Request) (*http.Response, error)) testRoundTripper {
return func(req *http.Request) (*http.Response, error) {
if req.URL.Path == "/_ping" {
return mockPingResponse(http.StatusOK, PingResult{
APIVersion: MaxAPIVersion,
OSType: runtime.GOOS,
Experimental: true,
BuilderVersion: build.BuilderBuildKit,
SwarmStatus: &SwarmStatus{
NodeState: swarm.LocalNodeStateActive,
ControlAvailable: true,
},
})(req)
}
resp, err := f(req)
if resp != nil {
if resp.Body == nil {
resp.Body = http.NoBody
}
if resp.Request == nil {
resp.Request = req
}
}
applyDefaultHeaders(resp)
return resp, err
}
}
// applyDefaultHeaders mocks the headers set by the daemon's VersionMiddleware.
func applyDefaultHeaders(resp *http.Response) {
if resp == nil {
return
}
if resp.Header == nil {
resp.Header = make(http.Header)
}
if resp.Header.Get("Server") == "" {
resp.Header.Set("Server", fmt.Sprintf("Docker/%s (%s)", "v99.99.99", runtime.GOOS))
}
if resp.Header.Get("Api-Version") == "" {
resp.Header.Set("Api-Version", MaxAPIVersion)
}
if resp.Header.Get("Ostype") == "" {
resp.Header.Set("Ostype", runtime.GOOS)
}
}
// WithMockClient is a test helper that allows you to inject a mock client for
// testing. By default, it mocks the "/_ping" endpoint, so allow the client
// to perform API-version negotiation. Other endpoints are handled by "doer".
func WithMockClient(doer func(*http.Request) (*http.Response, error)) Opt {
return WithHTTPClient(&http.Client{
Transport: makeTestRoundTripper(doer),
})
}
// WithBaseMockClient is a test helper that allows you to inject a mock client
// for testing. It is identical to [WithMockClient], but does not mock the "/_ping"
// endpoint, and doesn't set the default headers.
func WithBaseMockClient(doer func(*http.Request) (*http.Response, error)) Opt {
return WithHTTPClient(&http.Client{
Transport: ensureBody(doer),
})
@@ -78,21 +150,39 @@ func mockJSONResponse[T any](statusCode int, headers http.Header, resp T) func(r
return mockResponse(statusCode, hdr, string(respBody))
}
// mockPingResponse mocks the headers set for a "/_ping" response.
func mockPingResponse(statusCode int, ping PingResult) func(req *http.Request) (*http.Response, error) {
headers := http.Header{}
if s := ping.SwarmStatus; s != nil {
role := "worker"
if s.ControlAvailable {
role = "manager"
}
headers.Set("Swarm", fmt.Sprintf("%s/%s", string(swarm.LocalNodeStateActive), role))
}
headers.Set("Api-Version", ping.APIVersion)
headers.Set("Ostype", ping.OSType)
headers.Set("Docker-Experimental", strconv.FormatBool(ping.Experimental))
headers.Set("Builder-Version", string(ping.BuilderVersion))
headers.Set("Content-Type", "text/plain; charset=utf-8")
headers.Set("Cache-Control", "no-cache, no-store, must-revalidate")
headers.Set("Pragma", "no-cache")
return mockResponse(statusCode, headers, "OK")
}
func mockResponse(statusCode int, headers http.Header, respBody string) func(req *http.Request) (*http.Response, error) {
if headers == nil {
headers = make(http.Header)
}
var body io.ReadCloser
if respBody == "" {
body = http.NoBody
} else {
body = io.NopCloser(strings.NewReader(respBody))
}
return func(req *http.Request) (*http.Response, error) {
var body io.ReadCloser
if respBody == "" || req.Method == http.MethodHead {
body = http.NoBody
} else {
body = io.NopCloser(strings.NewReader(respBody))
}
return &http.Response{
Status: fmt.Sprintf("%d %s", statusCode, http.StatusText(statusCode)),
StatusCode: statusCode,
Header: headers,
Header: headers.Clone(),
Body: body,
Request: req,
}, nil

View File

@@ -38,14 +38,22 @@ type clientConfig struct {
userAgent *string
// custom HTTP headers configured by users.
customHTTPHeaders map[string]string
// manualOverride is set to true when the version was set by users.
manualOverride bool
// negotiateVersion indicates if the client should automatically negotiate
// the API version to use when making requests. API version negotiation is
// performed on the first request, after which negotiated is set to "true"
// so that subsequent requests do not re-negotiate.
negotiateVersion bool
// manualAPIVersion contains the API version set by users. This field
// will only be non-empty if a valid-formed version was set through
// [WithAPIVersion].
//
// If both manualAPIVersion and envAPIVersion are set, manualAPIVersion
// takes precedence. Either field disables API-version negotiation.
manualAPIVersion string
// envAPIVersion contains the API version set by users. This field
// will only be non-empty if a valid-formed version was set through
// [WithAPIVersionFromEnv].
//
// If both manualAPIVersion and envAPIVersion are set, manualAPIVersion
// takes precedence. Either field disables API-version negotiation.
envAPIVersion string
// traceOpts is a list of options to configure the tracing span.
traceOpts []otelhttp.Option
@@ -56,7 +64,7 @@ type Opt func(*clientConfig) error
// FromEnv configures the client with values from environment variables. It
// is the equivalent of using the [WithTLSClientConfigFromEnv], [WithHostFromEnv],
// and [WithVersionFromEnv] options.
// and [WithAPIVersionFromEnv] options.
//
// FromEnv uses the following environment variables:
//
@@ -71,7 +79,7 @@ func FromEnv(c *clientConfig) error {
ops := []Opt{
WithTLSClientConfigFromEnv(),
WithHostFromEnv(),
WithVersionFromEnv(),
WithAPIVersionFromEnv(),
}
for _, op := range ops {
if err := op(c); err != nil {
@@ -241,18 +249,59 @@ func WithTLSClientConfigFromEnv() Opt {
}
}
// WithVersion overrides the client version with the specified one. If an empty
// version is provided, the value is ignored to allow version negotiation
// (see [WithAPIVersionNegotiation]).
// WithAPIVersion overrides the client's API version with the specified one,
// and disables API version negotiation. If an empty version is provided,
// this option is ignored to allow version negotiation. The given version
// should be formatted "<major>.<minor>" (for example, "1.52"). It returns
// an error if the given value not in the correct format.
//
// WithVersion does not validate if the client supports the given version,
// and callers should verify if the version is in the correct format and
// lower than the maximum supported version as defined by [MaxAPIVersion].
func WithVersion(version string) Opt {
// WithAPIVersion does not validate if the client supports the given version,
// and callers should verify if the version lower than the maximum supported
// version as defined by [MaxAPIVersion].
//
// [WithAPIVersionFromEnv] takes precedence if [WithAPIVersion] and
// [WithAPIVersionFromEnv] are both set.
func WithAPIVersion(version string) Opt {
return func(c *clientConfig) error {
if v := strings.TrimPrefix(version, "v"); v != "" {
c.version = v
c.manualOverride = true
version = strings.TrimSpace(version)
if val := strings.TrimPrefix(version, "v"); val != "" {
ver, err := parseAPIVersion(val)
if err != nil {
return fmt.Errorf("invalid API version (%s): %w", version, err)
}
c.manualAPIVersion = ver
}
return nil
}
}
// WithVersion overrides the client version with the specified one.
//
// Deprecated: use [WithAPIVersion] instead.
func WithVersion(version string) Opt {
return WithAPIVersion(version)
}
// WithAPIVersionFromEnv overrides the client version with the version specified in
// the DOCKER_API_VERSION ([EnvOverrideAPIVersion]) environment variable.
// If DOCKER_API_VERSION is not set, or set to an empty value, the version
// is not modified.
//
// WithAPIVersion does not validate if the client supports the given version,
// and callers should verify if the version lower than the maximum supported
// version as defined by [MaxAPIVersion].
//
// [WithAPIVersionFromEnv] takes precedence if [WithAPIVersion] and
// [WithAPIVersionFromEnv] are both set.
func WithAPIVersionFromEnv() Opt {
return func(c *clientConfig) error {
version := strings.TrimSpace(os.Getenv(EnvOverrideAPIVersion))
if val := strings.TrimPrefix(version, "v"); val != "" {
ver, err := parseAPIVersion(val)
if err != nil {
return fmt.Errorf("invalid API version (%s): %w", version, err)
}
c.envAPIVersion = ver
}
return nil
}
@@ -260,25 +309,21 @@ func WithVersion(version string) Opt {
// WithVersionFromEnv overrides the client version with the version specified in
// the DOCKER_API_VERSION ([EnvOverrideAPIVersion]) environment variable.
// If DOCKER_API_VERSION is not set, or set to an empty value, the version
// is not modified.
//
// WithVersion does not validate if the client supports the given version,
// and callers should verify if the version is in the correct format and
// lower than the maximum supported version as defined by [MaxAPIVersion].
// Deprecated: use [WithAPIVersionFromEnv] instead.
func WithVersionFromEnv() Opt {
return func(c *clientConfig) error {
return WithVersion(os.Getenv(EnvOverrideAPIVersion))(c)
}
return WithAPIVersionFromEnv()
}
// WithAPIVersionNegotiation enables automatic API version negotiation for the client.
// With this option enabled, the client automatically negotiates the API version
// to use when making requests. API version negotiation is performed on the first
// request; subsequent requests do not re-negotiate.
//
// Deprecated: API-version negotiation is now enabled by default. Use [WithAPIVersion]
// or [WithAPIVersionFromEnv] to disable API version negotiation.
func WithAPIVersionNegotiation() Opt {
return func(c *clientConfig) error {
c.negotiateVersion = true
return nil
}
}

View File

@@ -0,0 +1,369 @@
package client
import (
"net/http"
"runtime"
"strings"
"testing"
"time"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestOptionWithHostFromEnv(t *testing.T) {
c, err := New(WithHostFromEnv())
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.basePath, ""))
if runtime.GOOS == "windows" {
assert.Check(t, is.Equal(c.host, "npipe:////./pipe/docker_engine"))
assert.Check(t, is.Equal(c.proto, "npipe"))
assert.Check(t, is.Equal(c.addr, "//./pipe/docker_engine"))
} else {
assert.Check(t, is.Equal(c.host, "unix:///var/run/docker.sock"))
assert.Check(t, is.Equal(c.proto, "unix"))
assert.Check(t, is.Equal(c.addr, "/var/run/docker.sock"))
}
t.Setenv("DOCKER_HOST", "tcp://foo.example.com:2376/test/")
c, err = New(WithHostFromEnv())
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.basePath, "/test/"))
assert.Check(t, is.Equal(c.host, "tcp://foo.example.com:2376/test/"))
assert.Check(t, is.Equal(c.proto, "tcp"))
assert.Check(t, is.Equal(c.addr, "foo.example.com:2376"))
}
func TestOptionWithTimeout(t *testing.T) {
timeout := 10 * time.Second
c, err := New(WithTimeout(timeout))
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.client.Timeout, timeout))
}
func TestOptionWithAPIVersion(t *testing.T) {
tests := []struct {
doc string
version string
expected string
expError string
}{
{
doc: "empty version",
version: "",
expected: MaxAPIVersion,
},
{
doc: "custom lower version with whitespace, no v-prefix",
version: " 1.50 ",
expected: "1.50",
},
{
// We currently allow downgrading the client to an unsupported lower version for testing.
doc: "downgrade unsupported version, no v-prefix",
version: "1.0",
expected: "1.0",
},
{
doc: "custom lower version, no v-prefix",
version: "1.50",
expected: "1.50",
},
{
// We currently allow upgrading the client to an unsupported higher version for testing.
doc: "upgrade version, no v-prefix",
version: "9.99",
expected: "9.99",
},
{
doc: "empty version, with v-prefix",
version: "v",
expected: MaxAPIVersion,
},
{
doc: "whitespace, with v-prefix",
version: " v1.0 ",
expected: "1.0",
},
{
doc: "downgrade unsupported version, with v-prefix",
version: "v1.0",
expected: "1.0",
},
{
doc: "custom lower version with whitespace and v-prefix",
version: " v1.50 ",
expected: "1.50",
},
{
doc: "custom lower version, with v-prefix",
version: "v1.50",
expected: "1.50",
},
{
doc: "upgrade version, with v-prefix",
version: "v9.99",
expected: "9.99",
},
{
doc: "malformed version",
version: "something-weird",
expError: "invalid API version (something-weird): must be formatted <major>.<minor>",
},
{
doc: "no minor",
version: "1",
expError: "invalid API version (1): must be formatted <major>.<minor>",
},
{
doc: "too many digits",
version: "1.2.3",
expError: "invalid API version (1.2.3): invalid minor version: must be formatted <major>.<minor>",
},
{
doc: "embedded whitespace",
version: "v 1.0",
expError: "invalid API version (v 1.0): invalid major version: must be formatted <major>.<minor>",
},
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
client, err := New(WithAPIVersion(tc.version))
if tc.expError != "" {
assert.Check(t, is.ErrorContains(err, tc.expError))
assert.Check(t, client == nil)
} else {
assert.NilError(t, err)
assert.Check(t, client != nil)
assert.Check(t, is.Equal(client.ClientVersion(), tc.expected))
isNoOp := strings.TrimPrefix(strings.TrimSpace(tc.version), "v") == ""
assert.Check(t, is.Equal(client.negotiated.Load(), !isNoOp))
}
})
}
}
func TestOptionWithAPIVersionFromEnv(t *testing.T) {
tests := []struct {
doc string
version string
expected string
expError string
}{
{
doc: "empty version",
version: "",
expected: MaxAPIVersion,
},
{
doc: "custom lower version with whitespace, no v-prefix",
version: " 1.50 ",
expected: "1.50",
},
{
// We currently allow downgrading the client to an unsupported lower version for testing.
doc: "downgrade unsupported version, no v-prefix",
version: "1.0",
expected: "1.0",
},
{
doc: "custom lower version, no v-prefix",
version: "1.50",
expected: "1.50",
},
{
// We currently allow upgrading the client to an unsupported higher version for testing.
doc: "upgrade version, no v-prefix",
version: "9.99",
expected: "9.99",
},
{
doc: "empty version, with v-prefix",
version: "v",
expected: MaxAPIVersion,
},
{
doc: "whitespace, with v-prefix",
version: " v1.0 ",
expected: "1.0",
},
{
doc: "downgrade unsupported version, with v-prefix",
version: "v1.0",
expected: "1.0",
},
{
doc: "custom lower version with whitespace and v-prefix",
version: " v1.50 ",
expected: "1.50",
},
{
doc: "custom lower version, with v-prefix",
version: "v1.50",
expected: "1.50",
},
{
doc: "upgrade version, with v-prefix",
version: "v9.99",
expected: "9.99",
},
{
doc: "malformed version",
version: "something-weird",
expError: "invalid API version (something-weird): must be formatted <major>.<minor>",
},
{
doc: "no minor",
version: "1",
expError: "invalid API version (1): must be formatted <major>.<minor>",
},
{
doc: "too many digits",
version: "1.2.3",
expError: "invalid API version (1.2.3): invalid minor version: must be formatted <major>.<minor>",
},
{
doc: "embedded whitespace",
version: "v 1.0",
expError: "invalid API version (v 1.0): invalid major version: must be formatted <major>.<minor>",
},
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
t.Setenv(EnvOverrideAPIVersion, tc.version)
client, err := New(WithAPIVersionFromEnv())
if tc.expError != "" {
assert.Check(t, is.ErrorContains(err, tc.expError))
assert.Check(t, client == nil)
} else {
assert.NilError(t, err)
assert.Check(t, client != nil)
assert.Check(t, is.Equal(client.ClientVersion(), tc.expected))
isNoOp := strings.TrimPrefix(strings.TrimSpace(tc.version), "v") == ""
assert.Check(t, is.Equal(client.negotiated.Load(), !isNoOp))
}
})
}
}
// TestOptionOverridePriority validates that overriding the API version through
// [WithAPIVersionFromEnv] takes precedence over other manual options, regardless
// the order in which they're passed.
func TestOptionOverridePriority(t *testing.T) {
t.Run("no env-var set", func(t *testing.T) {
client, err := New(WithAPIVersionFromEnv(), WithAPIVersion("1.50"))
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), "1.50"))
assert.Check(t, is.Equal(client.negotiated.Load(), true))
})
const expected = "1.51"
t.Setenv(EnvOverrideAPIVersion, expected)
t.Run("WithAPIVersionFromEnv first", func(t *testing.T) {
client, err := New(WithAPIVersionFromEnv(), WithAPIVersion("1.50"))
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), expected))
assert.Check(t, is.Equal(client.negotiated.Load(), true))
})
t.Run("WithAPIVersionFromEnv last", func(t *testing.T) {
client, err := New(WithAPIVersion("1.50"), WithAPIVersionFromEnv())
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), expected))
assert.Check(t, is.Equal(client.negotiated.Load(), true))
})
t.Run("FromEnv first", func(t *testing.T) {
client, err := New(FromEnv, WithAPIVersion("1.50"))
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), expected))
assert.Check(t, is.Equal(client.negotiated.Load(), true))
})
t.Run("FromEnv last", func(t *testing.T) {
client, err := New(WithAPIVersion("1.50"), FromEnv)
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), expected))
assert.Check(t, is.Equal(client.negotiated.Load(), true))
})
}
func TestWithUserAgent(t *testing.T) {
const userAgent = "Magic-Client/v1.2.3"
t.Run("user-agent", func(t *testing.T) {
c, err := New(
WithUserAgent(userAgent),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), userAgent))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("user-agent and custom headers", func(t *testing.T) {
c, err := New(
WithUserAgent(userAgent),
WithHTTPHeaders(map[string]string{"User-Agent": "should-be-ignored/1.0.0", "Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), userAgent))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("custom headers", func(t *testing.T) {
c, err := New(
WithHTTPHeaders(map[string]string{"User-Agent": "from-custom-headers/1.0.0", "Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), "from-custom-headers/1.0.0"))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("no user-agent set", func(t *testing.T) {
c, err := New(
WithHTTPHeaders(map[string]string{"Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), ""))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("reset custom user-agent", func(t *testing.T) {
c, err := New(
WithUserAgent(""),
WithHTTPHeaders(map[string]string{"User-Agent": "from-custom-headers/1.0.0", "Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), ""))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
}

View File

@@ -175,7 +175,7 @@ func TestGetAPIPath(t *testing.T) {
ctx := context.TODO()
for _, tc := range tests {
client, err := New(
WithVersion(tc.version),
WithAPIVersion(tc.version),
WithHost("tcp://localhost:2375"),
)
assert.NilError(t, err)
@@ -254,11 +254,10 @@ func TestNegotiateAPIVersionEmpty(t *testing.T) {
// if no version from server, expect the earliest
// version before APIVersion was implemented
const expected = fallbackAPIVersion
const expected = MinAPIVersion
client, err := New(FromEnv,
WithAPIVersionNegotiation(),
WithMockClient(mockResponse(http.StatusOK, http.Header{"Api-Version": []string{expected}}, "OK")),
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: expected})),
)
assert.NilError(t, err)
@@ -301,12 +300,10 @@ func TestNegotiateAPIVersion(t *testing.T) {
expectedVersion: "1.51",
},
{
// client should downgrade to the last version before version
// negotiation was added (1.24) if the daemon does not report
// a version.
// client should not downgrade if the daemon didn't report a version.
doc: "downgrade legacy",
pingVersion: "",
expectedVersion: fallbackAPIVersion,
expectedVersion: MaxAPIVersion,
},
{
// client should not downgrade to the version reported by the daemon
@@ -314,7 +311,7 @@ func TestNegotiateAPIVersion(t *testing.T) {
doc: "no downgrade old",
pingVersion: "1.19",
expectedVersion: MaxAPIVersion,
expectedErr: "API version 1.19 is not supported by this client: the minimum supported API version is " + fallbackAPIVersion,
expectedErr: "API version 1.19 is not supported by this client: the minimum supported API version is " + MinAPIVersion,
},
{
// client should not upgrade to a newer version if a version was set,
@@ -330,15 +327,14 @@ func TestNegotiateAPIVersion(t *testing.T) {
t.Run(tc.doc, func(t *testing.T) {
opts := []Opt{
FromEnv,
WithAPIVersionNegotiation(),
WithMockClient(mockResponse(http.StatusOK, http.Header{"Api-Version": []string{tc.pingVersion}}, "OK")),
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: tc.pingVersion})),
}
if tc.clientVersion != "" {
// Note that this check is redundant, as WithVersion() considers
// an empty version equivalent to "not setting a version", but
// doing this just to be explicit we are using the default.
opts = append(opts, WithVersion(tc.clientVersion))
opts = append(opts, WithAPIVersion(tc.clientVersion))
}
client, err := New(opts...)
assert.NilError(t, err)
@@ -363,7 +359,7 @@ func TestNegotiateAPIVersionOverride(t *testing.T) {
client, err := New(
FromEnv,
WithMockClient(mockResponse(http.StatusOK, http.Header{"Api-Version": []string{"1.45"}}, "OK")),
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: "1.45"})),
)
assert.NilError(t, err)
@@ -393,11 +389,9 @@ func TestNegotiateAPIVersionAutomatic(t *testing.T) {
ctx := t.Context()
client, err := New(
WithMockClient(func(req *http.Request) (*http.Response, error) {
hdr := http.Header{"Api-Version": []string{pingVersion}}
return mockResponse(http.StatusOK, hdr, "OK")(req)
WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
return mockPingResponse(http.StatusOK, PingResult{APIVersion: pingVersion})(req)
}),
WithAPIVersionNegotiation(),
)
assert.NilError(t, err)
@@ -422,8 +416,8 @@ func TestNegotiateAPIVersionAutomatic(t *testing.T) {
// with an empty version string does still allow API-version negotiation
func TestNegotiateAPIVersionWithEmptyVersion(t *testing.T) {
client, err := New(
WithVersion(""),
WithMockClient(mockResponse(http.StatusOK, http.Header{"Api-Version": []string{"1.50"}}, "OK")),
WithAPIVersion(""),
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: "1.50"})),
)
assert.NilError(t, err)
@@ -437,94 +431,28 @@ func TestNegotiateAPIVersionWithEmptyVersion(t *testing.T) {
// TestNegotiateAPIVersionWithFixedVersion asserts that initializing a client
// with a fixed version disables API-version negotiation
func TestNegotiateAPIVersionWithFixedVersion(t *testing.T) {
const customVersion = "1.50"
const (
customVersion = "1.50"
pingVersion = "1.49"
)
client, err := New(
WithVersion(customVersion),
WithMockClient(mockResponse(http.StatusOK, http.Header{"Api-Version": []string{"1.49"}}, "OK")),
WithAPIVersion(customVersion),
WithBaseMockClient(mockPingResponse(http.StatusOK, PingResult{APIVersion: pingVersion})),
)
assert.NilError(t, err)
_, err = client.Ping(t.Context(), PingOptions{
NegotiateAPIVersion: true,
})
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), customVersion))
_, err = client.Ping(t.Context(), PingOptions{
NegotiateAPIVersion: true,
ForceNegotiate: true,
})
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), customVersion))
}
// TestCustomAPIVersion tests initializing the client with a custom
// version.
func TestCustomAPIVersion(t *testing.T) {
tests := []struct {
doc string
version string
expected string
}{
{
doc: "empty version",
version: "",
expected: MaxAPIVersion,
},
{
doc: "custom lower version, no v-prefix",
version: "1.50",
expected: "1.50",
},
{
// We allow upgrading the client to an unsupported higher version for testing.
doc: "upgrade version, no v-prefix",
version: "9.99",
expected: "9.99",
},
{
// We currently ignore malformed versions.
doc: "empty version, with v-prefix",
version: "v",
expected: MaxAPIVersion,
},
{
doc: "custom lower version, with v-prefix",
version: "v1.50",
expected: "1.50",
},
{
// We allow upgrading the client to an unsupported higher version for testing.
doc: "upgrade version, with v-prefix",
version: "v9.99",
expected: "9.99",
},
{
// We currently allow downgrading the client to an unsupported lower version for testing.
doc: "downgrade unsupported version, no v-prefix",
version: "1.0",
expected: "1.0",
},
{
// We currently allow downgrading the client to an unsupported lower version for testing.
doc: "downgrade unsupported version, no v-prefix",
version: "v1.0",
expected: "1.0",
},
{
// When manually setting a version, no validation happens.
// so anything is accepted.
doc: "malformed version",
version: "something-weird",
expected: "something-weird",
},
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
client, err := New(WithVersion(tc.version))
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), tc.expected))
t.Setenv(EnvOverrideAPIVersion, tc.expected)
client, err = New(WithVersionFromEnv())
assert.NilError(t, err)
assert.Check(t, is.Equal(client.ClientVersion(), tc.expected))
})
}
assert.Check(t, is.Equal(client.ClientVersion(), pingVersion))
}
func TestClientRedirect(t *testing.T) {

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -17,7 +16,7 @@ func TestConfigCreateError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigCreate(context.Background(), ConfigCreateOptions{Spec: swarm.ConfigSpec{}})
_, err = client.ConfigCreate(t.Context(), ConfigCreateOptions{Spec: swarm.ConfigSpec{}})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -35,7 +34,7 @@ func TestConfigCreate(t *testing.T) {
)
assert.NilError(t, err)
r, err := client.ConfigCreate(context.Background(), ConfigCreateOptions{Spec: swarm.ConfigSpec{}})
r, err := client.ConfigCreate(t.Context(), ConfigCreateOptions{Spec: swarm.ConfigSpec{}})
assert.NilError(t, err)
assert.Check(t, is.Equal(r.ID, "test_config"))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"errors"
"net/http"
"testing"
@@ -18,7 +17,7 @@ func TestConfigInspectNotFound(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigInspect(context.Background(), "unknown", ConfigInspectOptions{})
_, err = client.ConfigInspect(t.Context(), "unknown", ConfigInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -29,11 +28,11 @@ func TestConfigInspectWithEmptyID(t *testing.T) {
}),
)
assert.NilError(t, err)
_, err = client.ConfigInspect(context.Background(), "", ConfigInspectOptions{})
_, err = client.ConfigInspect(t.Context(), "", ConfigInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ConfigInspect(context.Background(), " ", ConfigInspectOptions{})
_, err = client.ConfigInspect(t.Context(), " ", ConfigInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -44,7 +43,7 @@ func TestConfigInspectError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigInspect(context.Background(), "nothing", ConfigInspectOptions{})
_, err = client.ConfigInspect(t.Context(), "nothing", ConfigInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -54,7 +53,7 @@ func TestConfigInspectConfigNotFound(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigInspect(context.Background(), "unknown", ConfigInspectOptions{})
_, err = client.ConfigInspect(t.Context(), "unknown", ConfigInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -72,7 +71,7 @@ func TestConfigInspect(t *testing.T) {
)
assert.NilError(t, err)
result, err := client.ConfigInspect(context.Background(), "config_id", ConfigInspectOptions{})
result, err := client.ConfigInspect(t.Context(), "config_id", ConfigInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(result.Config.ID, "config_id"))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -18,7 +17,7 @@ func TestConfigListError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigList(context.Background(), ConfigListOptions{})
_, err = client.ConfigList(t.Context(), ConfigListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -67,7 +66,7 @@ func TestConfigList(t *testing.T) {
)
assert.NilError(t, err)
result, err := client.ConfigList(context.Background(), listCase.options)
result, err := client.ConfigList(t.Context(), listCase.options)
assert.NilError(t, err)
assert.Check(t, is.Len(result.Items, 2))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -16,14 +15,14 @@ func TestConfigRemoveError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigRemove(context.Background(), "config_id", ConfigRemoveOptions{})
_, err = client.ConfigRemove(t.Context(), "config_id", ConfigRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ConfigRemove(context.Background(), "", ConfigRemoveOptions{})
_, err = client.ConfigRemove(t.Context(), "", ConfigRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ConfigRemove(context.Background(), " ", ConfigRemoveOptions{})
_, err = client.ConfigRemove(t.Context(), " ", ConfigRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -41,6 +40,6 @@ func TestConfigRemove(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigRemove(context.Background(), "config_id", ConfigRemoveOptions{})
_, err = client.ConfigRemove(t.Context(), "config_id", ConfigRemoveOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -16,14 +15,14 @@ func TestConfigUpdateError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigUpdate(context.Background(), "config_id", ConfigUpdateOptions{})
_, err = client.ConfigUpdate(t.Context(), "config_id", ConfigUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ConfigUpdate(context.Background(), "", ConfigUpdateOptions{})
_, err = client.ConfigUpdate(t.Context(), "", ConfigUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ConfigUpdate(context.Background(), " ", ConfigUpdateOptions{})
_, err = client.ConfigUpdate(t.Context(), " ", ConfigUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -41,6 +40,6 @@ func TestConfigUpdate(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ConfigUpdate(context.Background(), "config_id", ConfigUpdateOptions{})
_, err = client.ConfigUpdate(t.Context(), "config_id", ConfigUpdateOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -18,14 +17,14 @@ func TestContainerCommitError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerCommit(context.Background(), "nothing", ContainerCommitOptions{})
_, err = client.ContainerCommit(t.Context(), "nothing", ContainerCommitOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerCommit(context.Background(), "", ContainerCommitOptions{})
_, err = client.ContainerCommit(t.Context(), "", ContainerCommitOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerCommit(context.Background(), " ", ContainerCommitOptions{})
_, err = client.ContainerCommit(t.Context(), " ", ContainerCommitOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -83,7 +82,7 @@ func TestContainerCommit(t *testing.T) {
)
assert.NilError(t, err)
r, err := client.ContainerCommit(context.Background(), expectedContainerID, ContainerCommitOptions{
r, err := client.ContainerCommit(t.Context(), expectedContainerID, ContainerCommitOptions{
Reference: specifiedReference,
Comment: expectedComment,
Author: expectedAuthor,

View File

@@ -2,7 +2,6 @@ package client
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
@@ -24,14 +23,14 @@ func TestContainerStatPathError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerStatPath(context.Background(), "container_id", ContainerStatPathOptions{Path: "path"})
_, err = client.ContainerStatPath(t.Context(), "container_id", ContainerStatPathOptions{Path: "path"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerStatPath(context.Background(), "", ContainerStatPathOptions{Path: "path"})
_, err = client.ContainerStatPath(t.Context(), "", ContainerStatPathOptions{Path: "path"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerStatPath(context.Background(), " ", ContainerStatPathOptions{Path: "path"})
_, err = client.ContainerStatPath(t.Context(), " ", ContainerStatPathOptions{Path: "path"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -42,7 +41,7 @@ func TestContainerStatPathNotFoundError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerStatPath(context.Background(), "container_id", ContainerStatPathOptions{Path: "path"})
_, err = client.ContainerStatPath(t.Context(), "container_id", ContainerStatPathOptions{Path: "path"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -52,7 +51,7 @@ func TestContainerStatPathNoHeaderError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerStatPath(context.Background(), "container_id", ContainerStatPathOptions{Path: "path/to/file"})
_, err = client.ContainerStatPath(t.Context(), "container_id", ContainerStatPathOptions{Path: "path/to/file"})
assert.Check(t, err != nil, "expected an error, got nothing")
}
@@ -86,7 +85,7 @@ func TestContainerStatPath(t *testing.T) {
}),
)
assert.NilError(t, err)
res, err := client.ContainerStatPath(context.Background(), "container_id", ContainerStatPathOptions{Path: expectedPath})
res, err := client.ContainerStatPath(t.Context(), "container_id", ContainerStatPathOptions{Path: expectedPath})
assert.NilError(t, err)
assert.Check(t, is.Equal(res.Stat.Name, "name"))
assert.Check(t, is.Equal(res.Stat.Mode, os.FileMode(0o700)))
@@ -98,20 +97,20 @@ func TestCopyToContainerError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyToContainer(context.Background(), "container_id", CopyToContainerOptions{
_, err = client.CopyToContainer(t.Context(), "container_id", CopyToContainerOptions{
DestinationPath: "path/to/file",
Content: bytes.NewReader([]byte("")),
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.CopyToContainer(context.Background(), "", CopyToContainerOptions{
_, err = client.CopyToContainer(t.Context(), "", CopyToContainerOptions{
DestinationPath: "path/to/file",
Content: bytes.NewReader([]byte("")),
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.CopyToContainer(context.Background(), " ", CopyToContainerOptions{
_, err = client.CopyToContainer(t.Context(), " ", CopyToContainerOptions{
DestinationPath: "path/to/file",
Content: bytes.NewReader([]byte("")),
})
@@ -125,7 +124,7 @@ func TestCopyToContainerNotFoundError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyToContainer(context.Background(), "container_id", CopyToContainerOptions{
_, err = client.CopyToContainer(t.Context(), "container_id", CopyToContainerOptions{
DestinationPath: "path/to/file",
Content: bytes.NewReader([]byte("")),
})
@@ -140,7 +139,7 @@ func TestCopyToContainerEmptyResponse(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyToContainer(context.Background(), "container_id", CopyToContainerOptions{
_, err = client.CopyToContainer(t.Context(), "container_id", CopyToContainerOptions{
DestinationPath: "path/to/file",
Content: bytes.NewReader([]byte("")),
})
@@ -183,7 +182,7 @@ func TestCopyToContainer(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyToContainer(context.Background(), "container_id", CopyToContainerOptions{
_, err = client.CopyToContainer(t.Context(), "container_id", CopyToContainerOptions{
DestinationPath: expectedPath,
Content: bytes.NewReader([]byte("content")),
AllowOverwriteDirWithFile: false,
@@ -197,14 +196,14 @@ func TestCopyFromContainerError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
_, err = client.CopyFromContainer(t.Context(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.CopyFromContainer(context.Background(), "", CopyFromContainerOptions{SourcePath: "path/to/file"})
_, err = client.CopyFromContainer(t.Context(), "", CopyFromContainerOptions{SourcePath: "path/to/file"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.CopyFromContainer(context.Background(), " ", CopyFromContainerOptions{SourcePath: "path/to/file"})
_, err = client.CopyFromContainer(t.Context(), " ", CopyFromContainerOptions{SourcePath: "path/to/file"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -215,7 +214,7 @@ func TestCopyFromContainerNotFoundError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
_, err = client.CopyFromContainer(t.Context(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -240,7 +239,7 @@ func TestCopyFromContainerEmptyResponse(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
_, err = client.CopyFromContainer(t.Context(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
assert.NilError(t, err)
}
@@ -250,7 +249,7 @@ func TestCopyFromContainerNoHeaderError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
_, err = client.CopyFromContainer(t.Context(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"})
assert.Check(t, err != nil, "expected an error, got nothing")
}
@@ -285,7 +284,7 @@ func TestCopyFromContainer(t *testing.T) {
}),
)
assert.NilError(t, err)
res2, err := client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: expectedPath})
res2, err := client.CopyFromContainer(t.Context(), "container_id", CopyFromContainerOptions{SourcePath: expectedPath})
assert.NilError(t, err)
assert.Check(t, is.Equal(res2.Stat.Name, "name"))
assert.Check(t, is.Equal(res2.Stat.Mode, os.FileMode(0o700)))

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"encoding/json"
"errors"
"fmt"
@@ -20,11 +19,11 @@ func TestContainerCreateError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: nil, Name: "nothing"})
_, err = client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: nil, Name: "nothing"})
assert.Error(t, err, "config.Image or Image is required")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{}, Name: "nothing"})
_, err = client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: &container.Config{}, Name: "nothing"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
}
@@ -34,7 +33,7 @@ func TestContainerCreateImageNotFound(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "unknown_image"}, Name: "unknown"})
_, err = client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: &container.Config{Image: "unknown_image"}, Name: "unknown"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -56,7 +55,7 @@ func TestContainerCreateWithName(t *testing.T) {
)
assert.NilError(t, err)
r, err := client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, Name: "container_name"})
r, err := client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, Name: "container_name"})
assert.NilError(t, err)
assert.Check(t, is.Equal(r.ID, "container_id"))
}
@@ -78,7 +77,7 @@ func TestContainerCreateAutoRemove(t *testing.T) {
)
assert.NilError(t, err)
resp, err := client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, HostConfig: &container.HostConfig{AutoRemove: true}})
resp, err := client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, HostConfig: &container.HostConfig{AutoRemove: true}})
assert.NilError(t, err)
assert.Check(t, is.Equal(resp.ID, "container_id"))
}
@@ -88,10 +87,10 @@ func TestContainerCreateAutoRemove(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestContainerCreateConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}})
_, err = client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: &container.Config{Image: "test"}})
assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
}
@@ -133,6 +132,6 @@ func TestContainerCreateCapabilities(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerCreate(context.Background(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, HostConfig: &container.HostConfig{CapAdd: inputCaps, CapDrop: inputCaps}})
_, err = client.ContainerCreate(t.Context(), ContainerCreateOptions{Config: &container.Config{Image: "test"}, HostConfig: &container.HostConfig{CapAdd: inputCaps, CapDrop: inputCaps}})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -17,14 +16,14 @@ func TestContainerDiffError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerDiff(context.Background(), "nothing", ContainerDiffOptions{})
_, err = client.ContainerDiff(t.Context(), "nothing", ContainerDiffOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerDiff(context.Background(), "", ContainerDiffOptions{})
_, err = client.ContainerDiff(t.Context(), "", ContainerDiffOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerDiff(context.Background(), " ", ContainerDiffOptions{})
_, err = client.ContainerDiff(t.Context(), " ", ContainerDiffOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -57,7 +56,7 @@ func TestContainerDiff(t *testing.T) {
)
assert.NilError(t, err)
result, err := client.ContainerDiff(context.Background(), "container_id", ContainerDiffOptions{})
result, err := client.ContainerDiff(t.Context(), "container_id", ContainerDiffOptions{})
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(result.Changes, expected))
}

View File

@@ -36,7 +36,7 @@ func TestExecCreateError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestExecCreateConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ExecCreate(t.Context(), "container_id", ExecCreateOptions{})

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -18,7 +17,7 @@ func TestContainerListError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.ContainerList(context.Background(), ContainerListOptions{})
_, err = client.ContainerList(t.Context(), ContainerListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -65,7 +64,7 @@ func TestContainerList(t *testing.T) {
)
assert.NilError(t, err)
list, err := client.ContainerList(context.Background(), ContainerListOptions{
list, err := client.ContainerList(t.Context(), ContainerListOptions{
Size: true,
All: true,
Since: "container",

View File

@@ -167,7 +167,7 @@ func TestContainerLogs(t *testing.T) {
}
func ExampleClient_ContainerLogs_withTimeout() {
client, err := New(FromEnv, WithAPIVersionNegotiation())
client, err := New(FromEnv)
if err != nil {
log.Fatal(err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -15,7 +14,7 @@ func TestContainerPruneError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ContainerPrune(context.Background(), ContainerPruneOptions{})
_, err = client.ContainerPrune(t.Context(), ContainerPruneOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -89,7 +88,7 @@ func TestContainerPrune(t *testing.T) {
}))
assert.NilError(t, err)
req, err := client.ContainerPrune(context.Background(), ContainerPruneOptions{Filters: listCase.filters})
req, err := client.ContainerPrune(t.Context(), ContainerPruneOptions{Filters: listCase.filters})
assert.NilError(t, err)
assert.Check(t, is.Len(req.Report.ContainersDeleted, 2))
assert.Check(t, is.Equal(uint64(9999), req.Report.SpaceReclaimed))

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -14,14 +13,14 @@ import (
func TestContainerRenameError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ContainerRename(context.Background(), "nothing", ContainerRenameOptions{NewName: "newNothing"})
_, err = client.ContainerRename(t.Context(), "nothing", ContainerRenameOptions{NewName: "newNothing"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerRename(context.Background(), "", ContainerRenameOptions{NewName: "newNothing"})
_, err = client.ContainerRename(t.Context(), "", ContainerRenameOptions{NewName: "newNothing"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerRename(context.Background(), " ", ContainerRenameOptions{NewName: "newNothing"})
_, err = client.ContainerRename(t.Context(), " ", ContainerRenameOptions{NewName: "newNothing"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -40,6 +39,6 @@ func TestContainerRename(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.ContainerRename(context.Background(), "container_id", ContainerRenameOptions{NewName: "newName"})
_, err = client.ContainerRename(t.Context(), "container_id", ContainerRenameOptions{NewName: "newName"})
assert.NilError(t, err)
}

View File

@@ -30,7 +30,7 @@ func TestContainerRestartError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestContainerRestartConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ContainerRestart(t.Context(), "nothing", ContainerRestartOptions{})

View File

@@ -30,7 +30,7 @@ func TestContainerStopError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestContainerStopConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ContainerStop(t.Context(), "container_id", ContainerStopOptions{})

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -15,14 +14,14 @@ import (
func TestContainerTopError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ContainerTop(context.Background(), "nothing", ContainerTopOptions{})
_, err = client.ContainerTop(t.Context(), "nothing", ContainerTopOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerTop(context.Background(), "", ContainerTopOptions{})
_, err = client.ContainerTop(t.Context(), "", ContainerTopOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerTop(context.Background(), " ", ContainerTopOptions{})
_, err = client.ContainerTop(t.Context(), " ", ContainerTopOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -54,7 +53,7 @@ func TestContainerTop(t *testing.T) {
}))
assert.NilError(t, err)
processList, err := client.ContainerTop(context.Background(), "container_id", ContainerTopOptions{
processList, err := client.ContainerTop(t.Context(), "container_id", ContainerTopOptions{
Arguments: []string{"arg1", "arg2"},
})
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ import (
func TestContainerUpdateError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ContainerUpdate(context.Background(), "nothing", ContainerUpdateOptions{})
_, err = client.ContainerUpdate(t.Context(), "nothing", ContainerUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerUpdate(context.Background(), "", ContainerUpdateOptions{})
_, err = client.ContainerUpdate(t.Context(), "", ContainerUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerUpdate(context.Background(), " ", ContainerUpdateOptions{})
_, err = client.ContainerUpdate(t.Context(), " ", ContainerUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,7 +36,7 @@ func TestContainerUpdate(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.ContainerUpdate(context.Background(), "container_id", ContainerUpdateOptions{
_, err = client.ContainerUpdate(t.Context(), "container_id", ContainerUpdateOptions{
Resources: &container.Resources{
CPUPeriod: 1,
},

View File

@@ -19,9 +19,12 @@ import (
)
func TestContainerWaitError(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
wait := client.ContainerWait(t.Context(), "nothing", ContainerWaitOptions{})
wait := client.ContainerWait(ctx, "nothing", ContainerWaitOptions{})
select {
case result := <-wait.Result:
t.Fatalf("expected to not get a wait result, got %d", result.StatusCode)
@@ -35,10 +38,13 @@ func TestContainerWaitError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestContainerWaitConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
wait := client.ContainerWait(t.Context(), "nothing", ContainerWaitOptions{})
wait := client.ContainerWait(ctx, "nothing", ContainerWaitOptions{})
select {
case result := <-wait.Result:
t.Fatalf("expected to not get a wait result, got %d", result.StatusCode)
@@ -48,6 +54,9 @@ func TestContainerWaitConnectionError(t *testing.T) {
}
func TestContainerWait(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
const expectedURL = "/containers/container_id/wait"
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
if err := assertRequest(req, http.MethodPost, expectedURL); err != nil {
@@ -59,7 +68,7 @@ func TestContainerWait(t *testing.T) {
}))
assert.NilError(t, err)
wait := client.ContainerWait(t.Context(), "container_id", ContainerWaitOptions{})
wait := client.ContainerWait(ctx, "container_id", ContainerWaitOptions{})
select {
case err := <-wait.Error:
assert.NilError(t, err)
@@ -69,6 +78,9 @@ func TestContainerWait(t *testing.T) {
}
func TestContainerWaitProxyInterrupt(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
const (
expectedURL = "/containers/container_id/wait"
expErr = "copying response body from Docker: unexpected EOF"
@@ -82,16 +94,19 @@ func TestContainerWaitProxyInterrupt(t *testing.T) {
}))
assert.NilError(t, err)
wait := client.ContainerWait(t.Context(), "container_id", ContainerWaitOptions{})
wait := client.ContainerWait(ctx, "container_id", ContainerWaitOptions{})
select {
case err := <-wait.Error:
assert.Check(t, is.ErrorContains(err, expErr))
case result := <-wait.Result:
t.Fatalf("Unexpected result: %v", result)
t.Errorf("Unexpected result: %v", result)
}
}
func TestContainerWaitProxyInterruptLong(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
const expectedURL = "/containers/container_id/wait"
msg := strings.Repeat("x", containerWaitErrorMsgLimit*5)
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
@@ -102,13 +117,13 @@ func TestContainerWaitProxyInterruptLong(t *testing.T) {
}))
assert.NilError(t, err)
wait := client.ContainerWait(t.Context(), "container_id", ContainerWaitOptions{})
wait := client.ContainerWait(ctx, "container_id", ContainerWaitOptions{})
select {
case err := <-wait.Error:
// LimitReader limiting isn't exact, because of how the Readers do chunking.
assert.Check(t, len(err.Error()) <= containerWaitErrorMsgLimit*2, "Expected error to be limited around %d, actual length: %d", containerWaitErrorMsgLimit, len(err.Error()))
case result := <-wait.Result:
t.Fatalf("Unexpected result: %v", result)
t.Errorf("Unexpected result: %v", result)
}
}
@@ -140,7 +155,7 @@ func TestContainerWaitErrorHandling(t *testing.T) {
assert.Check(t, is.Equal(err.Error(), test.exp.Error()))
return
case result := <-wait.Result:
t.Fatalf("expected to not get a wait result, got %d", result.StatusCode)
t.Errorf("expected to not get a wait result, got %d", result.StatusCode)
return
}
// Unexpected - we should not reach this line

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"errors"
"net/http"
"testing"
@@ -16,6 +15,6 @@ func TestDistributionInspectWithEmptyID(t *testing.T) {
return nil, errors.New("should not make request")
}))
assert.NilError(t, err)
_, err = client.DistributionInspect(context.Background(), "", DistributionInspectOptions{})
_, err = client.DistributionInspect(t.Context(), "", DistributionInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}

View File

@@ -13,7 +13,7 @@ const (
// be used to override the API version to use. Value must be
// formatted as MAJOR.MINOR, for example, "1.19".
//
// This env-var is read by [FromEnv] and [WithVersionFromEnv] and when set to a
// This env-var is read by [FromEnv] and [WithAPIVersionFromEnv] and when set to a
// non-empty value, takes precedence over API version negotiation.
//
// This environment variable should be used for debugging purposes only, as

View File

@@ -18,11 +18,24 @@ import (
func TestTLSCloseWriter(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
var chErr chan error
ts := &httptest.Server{Config: &http.Server{
ReadHeaderTimeout: 5 * time.Minute, // "G112: Potential Slowloris Attack (gosec)"; not a real concern for our use, so setting a long timeout.
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/_ping" {
resp, err := mockPingResponse(http.StatusOK, PingResult{APIVersion: MaxAPIVersion})(req)
if err != nil {
chErr <- fmt.Errorf("sending ping response: %w", err)
return
}
_ = resp.Header.Write(w)
w.WriteHeader(resp.StatusCode)
return
}
chErr = make(chan error, 1)
defer close(chErr)
@@ -38,12 +51,16 @@ func TestTLSCloseWriter(t *testing.T) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer conn.Close()
defer func() { _ = conn.Close() }()
// Flush the options to make sure the client sets the raw mode
_, _ = conn.Write([]byte{})
fmt.Fprint(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\n")
_, err = fmt.Fprint(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\n")
if err != nil {
chErr <- fmt.Errorf("writing update response: %w", err)
return
}
buf := make([]byte, 5)
_, err = conn.Read(buf)
@@ -72,7 +89,7 @@ func TestTLSCloseWriter(t *testing.T) {
assert.NilError(t, err)
ts.Listener = l
defer l.Close()
defer func() { _ = l.Close() }()
defer func() {
if chErr != nil {
@@ -86,10 +103,12 @@ func TestTLSCloseWriter(t *testing.T) {
serverURL, err := url.Parse(ts.URL)
assert.NilError(t, err)
client, err := New(WithHost("tcp://"+serverURL.Host), WithHTTPClient(ts.Client()))
httpClient := ts.Client()
defer httpClient.CloseIdleConnections()
client, err := New(WithHost("tcp://"+serverURL.Host), WithHTTPClient(httpClient))
assert.NilError(t, err)
resp, err := client.postHijacked(context.Background(), "/asdf", url.Values{}, nil, map[string][]string{"Content-Type": {"text/plain"}})
resp, err := client.postHijacked(ctx, "/asdf", url.Values{}, nil, map[string][]string{"Content-Type": {"text/plain"}})
assert.NilError(t, err)
defer resp.Close()

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"io"
"net/http"
@@ -18,7 +17,7 @@ import (
func TestImageBuildError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageBuild(context.Background(), nil, ImageBuildOptions{})
_, err = client.ImageBuild(t.Context(), nil, ImageBuildOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -188,7 +187,7 @@ func TestImageBuild(t *testing.T) {
return mockResponse(http.StatusOK, nil, "body")(req)
}))
assert.NilError(t, err)
buildResponse, err := client.ImageBuild(context.Background(), nil, buildCase.buildOptions)
buildResponse, err := client.ImageBuild(t.Context(), nil, buildCase.buildOptions)
assert.NilError(t, err)
response, err := io.ReadAll(buildResponse.Body)
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -15,7 +14,7 @@ import (
func TestImageHistoryError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageHistory(context.Background(), "nothing")
_, err = client.ImageHistory(t.Context(), "nothing")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -44,7 +43,7 @@ func TestImageHistory(t *testing.T) {
},
}
imageHistories, err := client.ImageHistory(context.Background(), "image_id", ImageHistoryWithPlatform(ocispec.Platform{
imageHistories, err := client.ImageHistory(t.Context(), "image_id", ImageHistoryWithPlatform(ocispec.Platform{
Architecture: "arm64",
OS: "linux",
Variant: "v8",

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"io"
"net/http"
"net/url"
@@ -17,7 +16,7 @@ import (
func TestImageImportError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageImport(context.Background(), ImageImportSource{}, "image:tag", ImageImportOptions{})
_, err = client.ImageImport(t.Context(), ImageImportSource{}, "image:tag", ImageImportOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -77,7 +76,7 @@ func TestImageImport(t *testing.T) {
return mockResponse(http.StatusOK, nil, expectedOutput)(req)
}))
assert.NilError(t, err)
result, err := client.ImageImport(context.Background(), ImageImportSource{
result, err := client.ImageImport(t.Context(), ImageImportSource{
Source: strings.NewReader("source"),
SourceName: "image_source",
}, "repository_name:imported", tc.options)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"errors"
"fmt"
"net/http"
@@ -18,7 +17,7 @@ func TestImageInspectError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageInspect(context.Background(), "nothing")
_, err = client.ImageInspect(t.Context(), "nothing")
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -26,7 +25,7 @@ func TestImageInspectImageNotFound(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusNotFound, "Server error")))
assert.NilError(t, err)
_, err = client.ImageInspect(context.Background(), "unknown")
_, err = client.ImageInspect(t.Context(), "unknown")
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -35,7 +34,7 @@ func TestImageInspectWithEmptyID(t *testing.T) {
return nil, errors.New("should not make request")
}))
assert.NilError(t, err)
_, err = client.ImageInspect(context.Background(), "")
_, err = client.ImageInspect(t.Context(), "")
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -53,7 +52,7 @@ func TestImageInspect(t *testing.T) {
}))
assert.NilError(t, err)
imageInspect, err := client.ImageInspect(context.Background(), "image_id")
imageInspect, err := client.ImageInspect(t.Context(), "image_id")
assert.NilError(t, err)
assert.Check(t, is.Equal(imageInspect.ID, "image_id"))
assert.Check(t, is.DeepEqual(imageInspect.RepoTags, expectedTags))
@@ -88,7 +87,7 @@ func TestImageInspectWithPlatform(t *testing.T) {
}))
assert.NilError(t, err)
imageInspect, err := client.ImageInspect(context.Background(), "image_id", ImageInspectWithPlatform(requestedPlatform))
imageInspect, err := client.ImageInspect(t.Context(), "image_id", ImageInspectWithPlatform(requestedPlatform))
assert.NilError(t, err)
assert.Check(t, is.Equal(imageInspect.ID, "image_id"))
assert.Check(t, is.Equal(imageInspect.Architecture, "arm64"))

View File

@@ -1,10 +1,8 @@
package client
import (
"context"
"fmt"
"net/http"
"net/url"
"testing"
cerrdefs "github.com/containerd/errdefs"
@@ -17,7 +15,7 @@ func TestImageListError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageList(context.Background(), ImageListOptions{})
_, err = client.ImageList(t.Context(), ImageListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -26,21 +24,23 @@ func TestImageListError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestImageListConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ImageList(context.Background(), ImageListOptions{})
_, err = client.ImageList(t.Context(), ImageListOptions{})
assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
}
func TestImageList(t *testing.T) {
const expectedURL = "/images/json"
listCases := []struct {
tests := []struct {
doc string
options ImageListOptions
expectedQueryParams map[string]string
}{
{
doc: "no options",
options: ImageListOptions{},
expectedQueryParams: map[string]string{
"all": "",
@@ -49,6 +49,7 @@ func TestImageList(t *testing.T) {
},
},
{
doc: "label filters and dangling",
options: ImageListOptions{
Filters: make(Filters).
Add("label", "label1").
@@ -62,6 +63,7 @@ func TestImageList(t *testing.T) {
},
},
{
doc: "label filters no dangling",
options: ImageListOptions{
Filters: make(Filters).Add("dangling", "false"),
},
@@ -71,59 +73,40 @@ func TestImageList(t *testing.T) {
"filters": `{"dangling":{"false":true}}`,
},
},
{
doc: "with shared size",
options: ImageListOptions{
SharedSize: true,
},
expectedQueryParams: map[string]string{
"shared-size": "1",
},
},
}
for _, listCase := range listCases {
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
if err := assertRequest(req, http.MethodGet, expectedURL); err != nil {
return nil, err
}
query := req.URL.Query()
for key, expected := range listCase.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
}
return mockJSONResponse(http.StatusOK, nil, []image.Summary{
{ID: "image_id2"},
{ID: "image_id2"},
})(req)
}))
assert.NilError(t, err)
images, err := client.ImageList(context.Background(), listCase.options)
assert.NilError(t, err)
assert.Check(t, is.Len(images.Items, 2))
}
}
// Checks if shared-size query parameter is set/not being set correctly
// for /images/json.
func TestImageListWithSharedSize(t *testing.T) {
t.Parallel()
const sharedSize = "shared-size"
for _, tc := range []struct {
name string
version string
options ImageListOptions
sharedSize string // expected value for the shared-size query param, or empty if it should not be set.
}{
{name: "unset, no options set"},
{name: "set", options: ImageListOptions{SharedSize: true}, sharedSize: "1"},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
var query url.Values
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
query = req.URL.Query()
return mockResponse(http.StatusOK, nil, "[]")(req)
}), WithVersion(tc.version))
if err := assertRequest(req, http.MethodGet, expectedURL); err != nil {
return nil, err
}
query := req.URL.Query()
for key, expected := range tc.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
}
return mockJSONResponse(http.StatusOK, nil, []image.Summary{
{ID: "image_id2"},
{ID: "image_id2"},
})(req)
}))
assert.NilError(t, err)
_, err = client.ImageList(context.Background(), tc.options)
defer func() { _ = client.Close() }()
images, err := client.ImageList(t.Context(), tc.options)
assert.NilError(t, err)
expectedSet := tc.sharedSize != ""
assert.Check(t, is.Equal(query.Has(sharedSize), expectedSet))
assert.Check(t, is.Equal(query.Get(sharedSize), tc.sharedSize))
assert.Check(t, is.Len(images.Items, 2))
})
}
}

View File

@@ -2,7 +2,6 @@ package client
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
@@ -19,7 +18,7 @@ func TestImageLoadError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageLoad(context.Background(), nil, ImageLoadWithQuiet(true))
_, err = client.ImageLoad(t.Context(), nil, ImageLoadWithQuiet(true))
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -81,7 +80,7 @@ func TestImageLoad(t *testing.T) {
assert.NilError(t, err)
input := bytes.NewReader([]byte(expectedInput))
imageLoadResponse, err := client.ImageLoad(context.Background(), input,
imageLoadResponse, err := client.ImageLoad(t.Context(), input,
ImageLoadWithQuiet(tc.quiet),
ImageLoadWithPlatforms(tc.platforms...),
)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestImagePruneError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImagePrune(context.Background(), ImagePruneOptions{})
_, err = client.ImagePrune(t.Context(), ImagePruneOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -83,7 +82,7 @@ func TestImagePrune(t *testing.T) {
}))
assert.NilError(t, err)
res, err := client.ImagePrune(context.Background(), ImagePruneOptions{Filters: listCase.filters})
res, err := client.ImagePrune(t.Context(), ImagePruneOptions{Filters: listCase.filters})
assert.NilError(t, err)
assert.Check(t, is.Len(res.Report.ImagesDeleted, 2))
assert.Check(t, is.Equal(uint64(9999), res.Report.SpaceReclaimed))

View File

@@ -23,28 +23,28 @@ func TestImagePullReferenceParseError(t *testing.T) {
}))
assert.NilError(t, err)
// An empty reference is an invalid reference
_, err = client.ImagePull(context.Background(), "", ImagePullOptions{})
_, err = client.ImagePull(t.Context(), "", ImagePullOptions{})
assert.Check(t, is.ErrorContains(err, "invalid reference format"))
}
func TestImagePullAnyError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImagePull(context.Background(), "myimage", ImagePullOptions{})
_, err = client.ImagePull(t.Context(), "myimage", ImagePullOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
func TestImagePullStatusUnauthorizedError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")))
assert.NilError(t, err)
_, err = client.ImagePull(context.Background(), "myimage", ImagePullOptions{})
_, err = client.ImagePull(t.Context(), "myimage", ImagePullOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
}
func TestImagePullWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")))
assert.NilError(t, err)
_, err = client.ImagePull(context.Background(), "myimage", ImagePullOptions{
_, err = client.ImagePull(t.Context(), "myimage", ImagePullOptions{
PrivilegeFunc: func(_ context.Context) (string, error) {
return "", errors.New("error requesting privilege")
},
@@ -55,7 +55,7 @@ func TestImagePullWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) {
func TestImagePullWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")))
assert.NilError(t, err)
_, err = client.ImagePull(context.Background(), "myimage", ImagePullOptions{
_, err = client.ImagePull(t.Context(), "myimage", ImagePullOptions{
PrivilegeFunc: staticAuth("a-auth-header"),
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
@@ -88,7 +88,7 @@ func TestImagePullWithPrivilegedFuncNoError(t *testing.T) {
return mockResponse(http.StatusOK, nil, "hello world")(req)
}))
assert.NilError(t, err)
resp, err := client.ImagePull(context.Background(), "myimage", ImagePullOptions{
resp, err := client.ImagePull(t.Context(), "myimage", ImagePullOptions{
RegistryAuth: invalidAuth,
PrivilegeFunc: staticAuth(validAuth),
})
@@ -177,7 +177,7 @@ func TestImagePullWithoutErrors(t *testing.T) {
return mockResponse(http.StatusOK, nil, expectedOutput)(req)
}))
assert.NilError(t, err)
resp, err := client.ImagePull(context.Background(), pullCase.reference, ImagePullOptions{
resp, err := client.ImagePull(t.Context(), pullCase.reference, ImagePullOptions{
All: pullCase.all,
})
assert.NilError(t, err)

View File

@@ -20,24 +20,24 @@ func TestImagePushReferenceError(t *testing.T) {
}))
assert.NilError(t, err)
// An empty reference is an invalid reference
_, err = client.ImagePush(context.Background(), "", ImagePushOptions{})
_, err = client.ImagePush(t.Context(), "", ImagePushOptions{})
assert.Check(t, is.ErrorContains(err, "invalid reference format"))
// A canonical reference cannot be pushed
_, err = client.ImagePush(context.Background(), "repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ImagePushOptions{})
_, err = client.ImagePush(t.Context(), "repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ImagePushOptions{})
assert.Check(t, is.Error(err, "cannot push a digest reference"))
}
func TestImagePushAnyError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImagePush(context.Background(), "myimage", ImagePushOptions{})
_, err = client.ImagePush(t.Context(), "myimage", ImagePushOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
func TestImagePushStatusUnauthorizedError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")))
assert.NilError(t, err)
_, err = client.ImagePush(context.Background(), "myimage", ImagePushOptions{})
_, err = client.ImagePush(t.Context(), "myimage", ImagePushOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
}
@@ -47,7 +47,7 @@ func TestImagePushWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) {
privilegeFunc := func(_ context.Context) (string, error) {
return "", errors.New("error requesting privilege")
}
_, err = client.ImagePush(context.Background(), "myimage", ImagePushOptions{
_, err = client.ImagePush(t.Context(), "myimage", ImagePushOptions{
PrivilegeFunc: privilegeFunc,
})
assert.Check(t, is.Error(err, "error requesting privilege"))
@@ -59,7 +59,7 @@ func TestImagePushWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T)
privilegeFunc := func(_ context.Context) (string, error) {
return "a-auth-header", nil
}
_, err = client.ImagePush(context.Background(), "myimage", ImagePushOptions{
_, err = client.ImagePush(t.Context(), "myimage", ImagePushOptions{
PrivilegeFunc: privilegeFunc,
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
@@ -88,7 +88,7 @@ func TestImagePushWithPrivilegedFuncNoError(t *testing.T) {
return mockResponse(http.StatusOK, nil, "hello world")(req)
}))
assert.NilError(t, err)
resp, err := client.ImagePush(context.Background(), "myname/myimage:tag", ImagePushOptions{
resp, err := client.ImagePush(t.Context(), "myname/myimage:tag", ImagePushOptions{
RegistryAuth: invalidAuth,
PrivilegeFunc: staticAuth(validAuth),
})
@@ -174,7 +174,7 @@ func TestImagePushWithoutErrors(t *testing.T) {
return mockResponse(http.StatusOK, nil, expectedOutput)(req)
}))
assert.NilError(t, err)
resp, err := client.ImagePush(context.Background(), tc.reference, ImagePushOptions{
resp, err := client.ImagePush(t.Context(), tc.reference, ImagePushOptions{
All: tc.all,
})
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -17,7 +16,7 @@ func TestImageRemoveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageRemove(context.Background(), "image_id", ImageRemoveOptions{})
_, err = client.ImageRemove(t.Context(), "image_id", ImageRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -25,7 +24,7 @@ func TestImageRemoveImageNotFound(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusNotFound, "no such image: unknown")))
assert.NilError(t, err)
_, err = client.ImageRemove(context.Background(), "unknown", ImageRemoveOptions{})
_, err = client.ImageRemove(t.Context(), "unknown", ImageRemoveOptions{})
assert.Check(t, is.ErrorContains(err, "no such image: unknown"))
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -91,7 +90,7 @@ func TestImageRemove(t *testing.T) {
opts.Platforms = []ocispec.Platform{*removeCase.platform}
}
res, err := client.ImageRemove(context.Background(), "image_id", opts)
res, err := client.ImageRemove(t.Context(), "image_id", opts)
assert.NilError(t, err)
assert.Check(t, is.Len(res.Items, 2))
}

View File

@@ -16,14 +16,14 @@ import (
func TestImageSearchAnyError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageSearch(context.Background(), "some-image", ImageSearchOptions{})
_, err = client.ImageSearch(t.Context(), "some-image", ImageSearchOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
func TestImageSearchStatusUnauthorizedError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")))
assert.NilError(t, err)
_, err = client.ImageSearch(context.Background(), "some-image", ImageSearchOptions{})
_, err = client.ImageSearch(t.Context(), "some-image", ImageSearchOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
}
@@ -33,7 +33,7 @@ func TestImageSearchWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) {
privilegeFunc := func(_ context.Context) (string, error) {
return "", errors.New("Error requesting privilege")
}
_, err = client.ImageSearch(context.Background(), "some-image", ImageSearchOptions{
_, err = client.ImageSearch(t.Context(), "some-image", ImageSearchOptions{
PrivilegeFunc: privilegeFunc,
})
assert.Check(t, is.Error(err, "Error requesting privilege"))
@@ -45,7 +45,7 @@ func TestImageSearchWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.
privilegeFunc := func(_ context.Context) (string, error) {
return "a-auth-header", nil
}
_, err = client.ImageSearch(context.Background(), "some-image", ImageSearchOptions{
_, err = client.ImageSearch(t.Context(), "some-image", ImageSearchOptions{
PrivilegeFunc: privilegeFunc,
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized))
@@ -77,7 +77,7 @@ func TestImageSearchWithPrivilegedFuncNoError(t *testing.T) {
privilegeFunc := func(_ context.Context) (string, error) {
return "IAmValid", nil
}
results, err := client.ImageSearch(context.Background(), "some-image", ImageSearchOptions{
results, err := client.ImageSearch(t.Context(), "some-image", ImageSearchOptions{
RegistryAuth: "NotValid",
PrivilegeFunc: privilegeFunc,
})
@@ -107,7 +107,7 @@ func TestImageSearchWithoutErrors(t *testing.T) {
})(req)
}))
assert.NilError(t, err)
results, err := client.ImageSearch(context.Background(), "some-image", ImageSearchOptions{
results, err := client.ImageSearch(t.Context(), "some-image", ImageSearchOptions{
Filters: make(Filters).Add("is-automated", "true").Add("stars", "3"),
})
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"math/rand"
"net/http"
@@ -16,7 +15,7 @@ func TestImageTagError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageTag(context.Background(), ImageTagOptions{Source: "image_id", Target: "repo:tag"})
_, err = client.ImageTag(t.Context(), ImageTagOptions{Source: "image_id", Target: "repo:tag"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -26,13 +25,13 @@ func TestImageTagInvalidReference(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ImageTag(context.Background(), ImageTagOptions{Source: "image_id", Target: "aa/asdf$$^/aa"})
_, err = client.ImageTag(t.Context(), ImageTagOptions{Source: "image_id", Target: "aa/asdf$$^/aa"})
assert.Check(t, is.Error(err, `error parsing reference: "aa/asdf$$^/aa" is not a valid repository/tag: invalid reference format`))
}
// Ensure we don't allow the use of invalid repository names or tags; these tag operations should fail.
func TestImageTagInvalidSourceImageName(t *testing.T) {
ctx := context.Background()
ctx := t.Context()
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "client should not have made an API call")))
assert.NilError(t, err)
@@ -89,7 +88,7 @@ func TestImageTagHexSource(t *testing.T) {
client, err := New(WithMockClient(mockResponse(http.StatusOK, nil, "OK")))
assert.NilError(t, err)
_, err = client.ImageTag(context.Background(), ImageTagOptions{Source: "0d409d33b27e47423b049f7f863faa08655a8c901749c2b25b93ca67d01a470d", Target: "repo:tag"})
_, err = client.ImageTag(t.Context(), ImageTagOptions{Source: "0d409d33b27e47423b049f7f863faa08655a8c901749c2b25b93ca67d01a470d", Target: "repo:tag"})
assert.NilError(t, err)
}
@@ -164,7 +163,7 @@ func TestImageTag(t *testing.T) {
return mockResponse(http.StatusOK, nil, "")(req)
}))
assert.NilError(t, err)
_, err = client.ImageTag(context.Background(), ImageTagOptions{Source: "image_id", Target: tagCase.reference})
_, err = client.ImageTag(t.Context(), ImageTagOptions{Source: "image_id", Target: tagCase.reference})
assert.NilError(t, err)
}
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"encoding/json"
"fmt"
"net/http"
@@ -17,19 +16,19 @@ func TestNetworkConnectError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NetworkConnect(context.Background(), "network_id", NetworkConnectOptions{
_, err = client.NetworkConnect(t.Context(), "network_id", NetworkConnectOptions{
Container: "container_id",
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
// Empty network ID or container ID
_, err = client.NetworkConnect(context.Background(), "", NetworkConnectOptions{
_, err = client.NetworkConnect(t.Context(), "", NetworkConnectOptions{
Container: "container_id",
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NetworkConnect(context.Background(), "network_id", NetworkConnectOptions{})
_, err = client.NetworkConnect(t.Context(), "network_id", NetworkConnectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -59,7 +58,7 @@ func TestNetworkConnectEmptyNilEndpointSettings(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.NetworkConnect(context.Background(), "network_id", NetworkConnectOptions{
_, err = client.NetworkConnect(t.Context(), "network_id", NetworkConnectOptions{
Container: "container_id",
})
assert.NilError(t, err)
@@ -94,7 +93,7 @@ func TestNetworkConnect(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.NetworkConnect(context.Background(), "network_id", NetworkConnectOptions{
_, err = client.NetworkConnect(t.Context(), "network_id", NetworkConnectOptions{
Container: "container_id",
EndpointConfig: &network.EndpointSettings{
NetworkID: "NetworkID",

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -15,7 +14,7 @@ func TestNetworkCreateError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NetworkCreate(context.Background(), "mynetwork", NetworkCreateOptions{})
_, err = client.NetworkCreate(t.Context(), "mynetwork", NetworkCreateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -24,10 +23,10 @@ func TestNetworkCreateError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestNetworkCreateConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.NetworkCreate(context.Background(), "mynetwork", NetworkCreateOptions{})
_, err = client.NetworkCreate(t.Context(), "mynetwork", NetworkCreateOptions{})
assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
}
@@ -46,7 +45,7 @@ func TestNetworkCreate(t *testing.T) {
assert.NilError(t, err)
enableIPv6 := true
networkResponse, err := client.NetworkCreate(context.Background(), "mynetwork", NetworkCreateOptions{
networkResponse, err := client.NetworkCreate(t.Context(), "mynetwork", NetworkCreateOptions{
Driver: "mydriver",
EnableIPv6: &enableIPv6,
Internal: true,

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"encoding/json"
"fmt"
"net/http"
@@ -17,19 +16,19 @@ func TestNetworkDisconnectError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NetworkDisconnect(context.Background(), "network_id", NetworkDisconnectOptions{
_, err = client.NetworkDisconnect(t.Context(), "network_id", NetworkDisconnectOptions{
Container: "container_id",
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
// Empty network ID or container ID
_, err = client.NetworkDisconnect(context.Background(), "", NetworkDisconnectOptions{
_, err = client.NetworkDisconnect(t.Context(), "", NetworkDisconnectOptions{
Container: "container_id",
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NetworkDisconnect(context.Background(), "network_id", NetworkDisconnectOptions{})
_, err = client.NetworkDisconnect(t.Context(), "network_id", NetworkDisconnectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -59,6 +58,6 @@ func TestNetworkDisconnect(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.NetworkDisconnect(context.Background(), "network_id", NetworkDisconnectOptions{Container: "container_id", Force: true})
_, err = client.NetworkDisconnect(t.Context(), "network_id", NetworkDisconnectOptions{Container: "container_id", Force: true})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"strings"
"testing"
@@ -52,39 +51,39 @@ func TestNetworkInspect(t *testing.T) {
t.Run("empty ID", func(t *testing.T) {
// verify that the client does not create a request if the network-ID/name is empty.
_, err := client.NetworkInspect(context.Background(), "", NetworkInspectOptions{})
_, err := client.NetworkInspect(t.Context(), "", NetworkInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NetworkInspect(context.Background(), " ", NetworkInspectOptions{})
_, err = client.NetworkInspect(t.Context(), " ", NetworkInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
})
t.Run("no options", func(t *testing.T) {
r, err := client.NetworkInspect(context.Background(), "network_id", NetworkInspectOptions{})
r, err := client.NetworkInspect(t.Context(), "network_id", NetworkInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(r.Network.Name, "mynetwork"))
})
t.Run("verbose", func(t *testing.T) {
r, err := client.NetworkInspect(context.Background(), "network_id", NetworkInspectOptions{Verbose: true})
r, err := client.NetworkInspect(t.Context(), "network_id", NetworkInspectOptions{Verbose: true})
assert.NilError(t, err)
assert.Check(t, is.Equal(r.Network.Name, "mynetwork"))
_, ok := r.Network.Services["web"]
assert.Check(t, ok, "expected service `web` missing in the verbose output")
})
t.Run("global scope", func(t *testing.T) {
_, err := client.NetworkInspect(context.Background(), "network_id", NetworkInspectOptions{Scope: "global"})
_, err := client.NetworkInspect(t.Context(), "network_id", NetworkInspectOptions{Scope: "global"})
assert.Check(t, is.ErrorContains(err, "Error: No such network: network_id"))
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
})
t.Run("unknown network", func(t *testing.T) {
_, err := client.NetworkInspect(context.Background(), "unknown", NetworkInspectOptions{})
_, err := client.NetworkInspect(t.Context(), "unknown", NetworkInspectOptions{})
assert.Check(t, is.ErrorContains(err, "Error: No such network: unknown"))
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
})
t.Run("server error", func(t *testing.T) {
// Just testing that an internal server error is converted correctly by the client
_, err := client.NetworkInspect(context.Background(), "test-500-response", NetworkInspectOptions{})
_, err := client.NetworkInspect(t.Context(), "test-500-response", NetworkInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
})
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestNetworkListError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NetworkList(context.Background(), NetworkListOptions{})
_, err = client.NetworkList(t.Context(), NetworkListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -74,7 +73,7 @@ func TestNetworkList(t *testing.T) {
}))
assert.NilError(t, err)
res, err := client.NetworkList(context.Background(), listCase.options)
res, err := client.NetworkList(t.Context(), listCase.options)
assert.NilError(t, err)
assert.Check(t, is.Len(res.Items, 1))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -17,7 +16,7 @@ func TestNetworkPruneError(t *testing.T) {
)
assert.NilError(t, err)
_, err = client.NetworkPrune(context.Background(), NetworkPruneOptions{})
_, err = client.NetworkPrune(t.Context(), NetworkPruneOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -82,7 +81,7 @@ func TestNetworkPrune(t *testing.T) {
)
assert.NilError(t, err)
res, err := client.NetworkPrune(context.Background(), NetworkPruneOptions{Filters: listCase.filters})
res, err := client.NetworkPrune(t.Context(), NetworkPruneOptions{Filters: listCase.filters})
assert.NilError(t, err)
assert.Check(t, is.Len(res.Report.NetworksDeleted, 2))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestNetworkRemoveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NetworkRemove(context.Background(), "network_id", NetworkRemoveOptions{})
_, err = client.NetworkRemove(t.Context(), "network_id", NetworkRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.NetworkRemove(context.Background(), "", NetworkRemoveOptions{})
_, err = client.NetworkRemove(t.Context(), "", NetworkRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NetworkRemove(context.Background(), " ", NetworkRemoveOptions{})
_, err = client.NetworkRemove(t.Context(), " ", NetworkRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestNetworkRemove(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.NetworkRemove(context.Background(), "network_id", NetworkRemoveOptions{})
_, err = client.NetworkRemove(t.Context(), "network_id", NetworkRemoveOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"errors"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestNodeInspectError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NodeInspect(context.Background(), "nothing", NodeInspectOptions{})
_, err = client.NodeInspect(t.Context(), "nothing", NodeInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -24,7 +23,7 @@ func TestNodeInspectNodeNotFound(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusNotFound, "Server error")))
assert.NilError(t, err)
_, err = client.NodeInspect(context.Background(), "unknown", NodeInspectOptions{})
_, err = client.NodeInspect(t.Context(), "unknown", NodeInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -33,11 +32,11 @@ func TestNodeInspectWithEmptyID(t *testing.T) {
return nil, errors.New("should not make request")
}))
assert.NilError(t, err)
_, err = client.NodeInspect(context.Background(), "", NodeInspectOptions{})
_, err = client.NodeInspect(t.Context(), "", NodeInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NodeInspect(context.Background(), " ", NodeInspectOptions{})
_, err = client.NodeInspect(t.Context(), " ", NodeInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -54,7 +53,7 @@ func TestNodeInspect(t *testing.T) {
}))
assert.NilError(t, err)
result, err := client.NodeInspect(context.Background(), "node_id", NodeInspectOptions{})
result, err := client.NodeInspect(t.Context(), "node_id", NodeInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(result.Node.ID, "node_id"))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestNodeListError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NodeList(context.Background(), NodeListOptions{})
_, err = client.NodeList(t.Context(), NodeListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -62,7 +61,7 @@ func TestNodeList(t *testing.T) {
}))
assert.NilError(t, err)
result, err := client.NodeList(context.Background(), listCase.options)
result, err := client.NodeList(t.Context(), listCase.options)
assert.NilError(t, err)
assert.Check(t, is.Len(result.Items, 2))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -15,14 +14,14 @@ func TestNodeRemoveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NodeRemove(context.Background(), "node_id", NodeRemoveOptions{Force: false})
_, err = client.NodeRemove(t.Context(), "node_id", NodeRemoveOptions{Force: false})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.NodeRemove(context.Background(), "", NodeRemoveOptions{Force: false})
_, err = client.NodeRemove(t.Context(), "", NodeRemoveOptions{Force: false})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NodeRemove(context.Background(), " ", NodeRemoveOptions{Force: false})
_, err = client.NodeRemove(t.Context(), " ", NodeRemoveOptions{Force: false})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -57,7 +56,7 @@ func TestNodeRemove(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.NodeRemove(context.Background(), "node_id", NodeRemoveOptions{Force: removeCase.force})
_, err = client.NodeRemove(t.Context(), "node_id", NodeRemoveOptions{Force: removeCase.force})
assert.NilError(t, err)
}
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -15,20 +14,20 @@ func TestNodeUpdateError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.NodeUpdate(context.Background(), "node_id", NodeUpdateOptions{
_, err = client.NodeUpdate(t.Context(), "node_id", NodeUpdateOptions{
Version: swarm.Version{},
Spec: swarm.NodeSpec{},
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.NodeUpdate(context.Background(), "", NodeUpdateOptions{
_, err = client.NodeUpdate(t.Context(), "", NodeUpdateOptions{
Version: swarm.Version{},
Spec: swarm.NodeSpec{},
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NodeUpdate(context.Background(), " ", NodeUpdateOptions{
_, err = client.NodeUpdate(t.Context(), " ", NodeUpdateOptions{
Version: swarm.Version{},
Spec: swarm.NodeSpec{},
})
@@ -47,7 +46,7 @@ func TestNodeUpdate(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.NodeUpdate(context.Background(), "node_id", NodeUpdateOptions{
_, err = client.NodeUpdate(t.Context(), "node_id", NodeUpdateOptions{
Version: swarm.Version{},
Spec: swarm.NodeSpec{},
})

View File

@@ -1,136 +0,0 @@
package client
import (
"net/http"
"runtime"
"testing"
"time"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestOptionWithHostFromEnv(t *testing.T) {
c, err := New(WithHostFromEnv())
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.basePath, ""))
if runtime.GOOS == "windows" {
assert.Check(t, is.Equal(c.host, "npipe:////./pipe/docker_engine"))
assert.Check(t, is.Equal(c.proto, "npipe"))
assert.Check(t, is.Equal(c.addr, "//./pipe/docker_engine"))
} else {
assert.Check(t, is.Equal(c.host, "unix:///var/run/docker.sock"))
assert.Check(t, is.Equal(c.proto, "unix"))
assert.Check(t, is.Equal(c.addr, "/var/run/docker.sock"))
}
t.Setenv("DOCKER_HOST", "tcp://foo.example.com:2376/test/")
c, err = New(WithHostFromEnv())
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.basePath, "/test/"))
assert.Check(t, is.Equal(c.host, "tcp://foo.example.com:2376/test/"))
assert.Check(t, is.Equal(c.proto, "tcp"))
assert.Check(t, is.Equal(c.addr, "foo.example.com:2376"))
}
func TestOptionWithTimeout(t *testing.T) {
timeout := 10 * time.Second
c, err := New(WithTimeout(timeout))
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.client.Timeout, timeout))
}
func TestOptionWithVersionFromEnv(t *testing.T) {
c, err := New(WithVersionFromEnv())
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.version, MaxAPIVersion))
assert.Check(t, is.Equal(c.manualOverride, false))
t.Setenv("DOCKER_API_VERSION", "2.9999")
c, err = New(WithVersionFromEnv())
assert.NilError(t, err)
assert.Check(t, c.client != nil)
assert.Check(t, is.Equal(c.version, "2.9999"))
assert.Check(t, is.Equal(c.manualOverride, true))
}
func TestWithUserAgent(t *testing.T) {
const userAgent = "Magic-Client/v1.2.3"
t.Run("user-agent", func(t *testing.T) {
c, err := New(
WithUserAgent(userAgent),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), userAgent))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("user-agent and custom headers", func(t *testing.T) {
c, err := New(
WithUserAgent(userAgent),
WithHTTPHeaders(map[string]string{"User-Agent": "should-be-ignored/1.0.0", "Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), userAgent))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("custom headers", func(t *testing.T) {
c, err := New(
WithHTTPHeaders(map[string]string{"User-Agent": "from-custom-headers/1.0.0", "Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), "from-custom-headers/1.0.0"))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("no user-agent set", func(t *testing.T) {
c, err := New(
WithHTTPHeaders(map[string]string{"Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), ""))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
t.Run("reset custom user-agent", func(t *testing.T) {
c, err := New(
WithUserAgent(""),
WithHTTPHeaders(map[string]string{"User-Agent": "from-custom-headers/1.0.0", "Other-Header": "hello-world"}),
WithMockClient(func(req *http.Request) (*http.Response, error) {
assert.Check(t, is.Equal(req.Header.Get("User-Agent"), ""))
assert.Check(t, is.Equal(req.Header.Get("Other-Header"), "hello-world"))
return &http.Response{StatusCode: http.StatusOK}, nil
}),
)
assert.NilError(t, err)
_, err = c.Ping(t.Context(), PingOptions{})
assert.NilError(t, err)
assert.NilError(t, c.Close())
})
}

View File

@@ -20,7 +20,7 @@ type PingOptions struct {
//
// If a manual override is in place, either through the "DOCKER_API_VERSION"
// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
// with a fixed version ([WithVersion]), no negotiation is performed.
// with a fixed version ([WithAPIVersion]), no negotiation is performed.
//
// If the API server's ping response does not contain an API version, or if the
// client did not get a successful ping response, it assumes it is connected with
@@ -29,9 +29,8 @@ type PingOptions struct {
NegotiateAPIVersion bool
// ForceNegotiate forces the client to re-negotiate the API version, even if
// API-version negotiation already happened. This option cannot be
// used if the client is configured with a fixed version using (using
// [WithVersion] or [WithVersionFromEnv]).
// API-version negotiation already happened or it the client is configured
// with a fixed version (using [WithAPIVersion] or [WithAPIVersionFromEnv]).
//
// This option has no effect if NegotiateAPIVersion is not set.
ForceNegotiate bool
@@ -72,10 +71,12 @@ type SwarmStatus struct {
// for other non-success status codes, failing to connect to the API, or failing
// to parse the API response.
func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, error) {
if cli.manualOverride {
if !options.NegotiateAPIVersion {
// No API version negotiation needed; just return ping response.
return cli.ping(ctx)
}
if !options.NegotiateAPIVersion && !cli.negotiateVersion {
if cli.negotiated.Load() && !options.ForceNegotiate {
// API version was already negotiated or manually set.
return cli.ping(ctx)
}
@@ -85,10 +86,19 @@ func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, e
ping, err := cli.ping(ctx)
if err != nil {
return cli.ping(ctx)
return ping, err
}
if cli.negotiated.Load() && !options.ForceNegotiate {
// API version was already negotiated or manually set.
//
// We check cli.negotiated again under lock, to account for race
// conditions with the check at the start of this function.
return ping, nil
}
if ping.APIVersion == "" {
cli.setAPIVersion(MaxAPIVersion)
return ping, nil
}
@@ -112,10 +122,15 @@ func (cli *Client) ping(ctx context.Context) (PingResult, error) {
// response-body to get error details from.
return newPingResult(resp), nil
}
// close to allow reusing connection.
ensureReaderClosed(resp)
// HEAD failed or returned a non-OK status; fallback to GET.
req.Method = http.MethodGet
resp, err = cli.doRequest(req)
req2, err := cli.buildRequest(ctx, http.MethodGet, path.Join(cli.basePath, "/_ping"), nil, nil)
if err != nil {
return PingResult{}, err
}
resp, err = cli.doRequest(req2)
defer ensureReaderClosed(resp)
if err != nil {
// Failed to connect.

View File

@@ -18,7 +18,7 @@ import (
// panics.
func TestPingFail(t *testing.T) {
var withHeader bool
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
var hdr http.Header
if withHeader {
hdr = http.Header{}
@@ -48,7 +48,7 @@ func TestPingFail(t *testing.T) {
// TestPingWithError tests the case where there is a protocol error in the ping.
// This test is mostly just testing that there are no panics in this code path.
func TestPingWithError(t *testing.T) {
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
return nil, errors.New("some connection error")
}))
assert.NilError(t, err)
@@ -64,7 +64,7 @@ func TestPingWithError(t *testing.T) {
// TestPingSuccess tests that we are able to get the expected API headers/ping
// details on success.
func TestPingSuccess(t *testing.T) {
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
hdr := http.Header{}
hdr.Set("Api-Version", "awesome")
hdr.Set("Docker-Experimental", "true")
@@ -76,6 +76,7 @@ func TestPingSuccess(t *testing.T) {
assert.NilError(t, err)
assert.Check(t, is.Equal(true, ping.Experimental))
assert.Check(t, is.Equal("awesome", ping.APIVersion))
assert.Check(t, is.Equal(MaxAPIVersion, client.version))
assert.Check(t, is.Equal(SwarmStatus{NodeState: "active", ControlAvailable: true}, *ping.SwarmStatus))
}
@@ -109,7 +110,7 @@ func TestPingHeadFallback(t *testing.T) {
for _, tc := range tests {
t.Run(http.StatusText(tc.status), func(t *testing.T) {
var reqs []string
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedPath) {
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedPath, req.URL.Path)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestPluginDisableError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.PluginDisable(context.Background(), "plugin_name", PluginDisableOptions{})
_, err = client.PluginDisable(t.Context(), "plugin_name", PluginDisableOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.PluginDisable(context.Background(), "", PluginDisableOptions{})
_, err = client.PluginDisable(t.Context(), "", PluginDisableOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.PluginDisable(context.Background(), " ", PluginDisableOptions{})
_, err = client.PluginDisable(t.Context(), " ", PluginDisableOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestPluginDisable(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.PluginDisable(context.Background(), "plugin_name", PluginDisableOptions{})
_, err = client.PluginDisable(t.Context(), "plugin_name", PluginDisableOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestPluginEnableError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.PluginEnable(context.Background(), "plugin_name", PluginEnableOptions{})
_, err = client.PluginEnable(t.Context(), "plugin_name", PluginEnableOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.PluginEnable(context.Background(), "", PluginEnableOptions{})
_, err = client.PluginEnable(t.Context(), "", PluginEnableOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.PluginEnable(context.Background(), " ", PluginEnableOptions{})
_, err = client.PluginEnable(t.Context(), " ", PluginEnableOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestPluginEnable(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.PluginEnable(context.Background(), "plugin_name", PluginEnableOptions{})
_, err = client.PluginEnable(t.Context(), "plugin_name", PluginEnableOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestPluginListError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.PluginList(context.Background(), PluginListOptions{})
_, err = client.PluginList(t.Context(), PluginListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -71,7 +70,7 @@ func TestPluginList(t *testing.T) {
}))
assert.NilError(t, err)
list, err := client.PluginList(context.Background(), PluginListOptions{
list, err := client.PluginList(t.Context(), PluginListOptions{
Filters: listCase.filters,
})
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -16,14 +15,14 @@ func TestPluginPushError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.PluginPush(context.Background(), "plugin_name", PluginPushOptions{})
_, err = client.PluginPush(t.Context(), "plugin_name", PluginPushOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.PluginPush(context.Background(), "", PluginPushOptions{})
_, err = client.PluginPush(t.Context(), "", PluginPushOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.PluginPush(context.Background(), " ", PluginPushOptions{})
_, err = client.PluginPush(t.Context(), " ", PluginPushOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -43,6 +42,6 @@ func TestPluginPush(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.PluginPush(context.Background(), "plugin_name", PluginPushOptions{RegistryAuth: "authtoken"})
_, err = client.PluginPush(t.Context(), "plugin_name", PluginPushOptions{RegistryAuth: "authtoken"})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestPluginRemoveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.PluginRemove(context.Background(), "plugin_name", PluginRemoveOptions{})
_, err = client.PluginRemove(t.Context(), "plugin_name", PluginRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.PluginRemove(context.Background(), "", PluginRemoveOptions{})
_, err = client.PluginRemove(t.Context(), "", PluginRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.PluginRemove(context.Background(), " ", PluginRemoveOptions{})
_, err = client.PluginRemove(t.Context(), " ", PluginRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestPluginRemove(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.PluginRemove(context.Background(), "plugin_name", PluginRemoveOptions{})
_, err = client.PluginRemove(t.Context(), "plugin_name", PluginRemoveOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestPluginSetError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.PluginSet(context.Background(), "plugin_name", PluginSetOptions{})
_, err = client.PluginSet(t.Context(), "plugin_name", PluginSetOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.PluginSet(context.Background(), "", PluginSetOptions{})
_, err = client.PluginSet(t.Context(), "", PluginSetOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.PluginSet(context.Background(), " ", PluginSetOptions{})
_, err = client.PluginSet(t.Context(), " ", PluginSetOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestPluginSet(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.PluginSet(context.Background(), "plugin_name", PluginSetOptions{Args: []string{"arg1"}})
_, err = client.PluginSet(t.Context(), "plugin_name", PluginSetOptions{Args: []string{"arg1"}})
assert.NilError(t, err)
}

View File

@@ -67,31 +67,22 @@ func (cli *Client) delete(ctx context.Context, path string, query url.Values, he
// prepareJSONRequest encodes the given body to JSON and returns it as an [io.Reader], and sets the Content-Type
// header. If body is nil, or a nil-interface, a "nil" body is returned without
// error.
//
// TODO(thaJeztah): should this return an error if a different Content-Type is already set?
// TODO(thaJeztah): is "nil" the appropriate approach for an empty body, or should we use [http.NoBody] (or similar)?
func prepareJSONRequest(body any, headers http.Header) (io.Reader, http.Header, error) {
if body == nil {
return nil, headers, nil
}
// encoding/json encodes a nil pointer as the JSON document `null`,
// irrespective of whether the type implements json.Marshaler or encoding.TextMarshaler.
// That is almost certainly not what the caller intended as the request body.
//
// TODO(thaJeztah): consider moving this to jsonEncode, which would also allow returning an (empty) reader instead of nil.
if reflect.TypeOf(body).Kind() == reflect.Ptr && reflect.ValueOf(body).IsNil() {
return nil, headers, nil
}
jsonBody, err := jsonEncode(body)
if err != nil {
return nil, headers, err
}
if jsonBody == nil || jsonBody == http.NoBody {
// no content-type is set on empty requests.
return jsonBody, headers, nil
}
hdr := http.Header{}
if headers != nil {
hdr = headers.Clone()
}
// TODO(thaJeztah): should this return an error if a different Content-Type is already set?
hdr.Set("Content-Type", "application/json")
return jsonBody, hdr, nil
}
@@ -110,9 +101,6 @@ func (cli *Client) buildRequest(ctx context.Context, method, path string, body i
req.Host = DummyHost
}
if body != nil && req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "text/plain")
}
return req, nil
}
@@ -248,7 +236,11 @@ func checkResponseErr(serverResp *http.Response) (retErr error) {
if statusMsg == "" {
statusMsg = http.StatusText(serverResp.StatusCode)
}
if serverResp.Body != nil {
var reqMethod string
if serverResp.Request != nil {
reqMethod = serverResp.Request.Method
}
if serverResp.Body != nil && reqMethod != http.MethodHead {
bodyMax := 1 * 1024 * 1024 // 1 MiB
bodyR := &io.LimitedReader{
R: serverResp.Body,
@@ -333,25 +325,49 @@ func (cli *Client) addHeaders(req *http.Request, headers http.Header) *http.Requ
}
func jsonEncode(data any) (io.Reader, error) {
var params bytes.Buffer
if data != nil {
if err := json.NewEncoder(&params).Encode(data); err != nil {
return nil, err
switch x := data.(type) {
case nil:
return http.NoBody, nil
case io.Reader:
// http.NoBody or other readers
return x, nil
case json.RawMessage:
if len(x) == 0 {
return http.NoBody, nil
}
return bytes.NewReader(x), nil
}
return &params, nil
// encoding/json encodes a nil pointer as the JSON document `null`,
// irrespective of whether the type implements json.Marshaler or encoding.TextMarshaler.
// That is almost certainly not what the caller intended as the request body.
if v := reflect.ValueOf(data); v.Kind() == reflect.Ptr && v.IsNil() {
return http.NoBody, nil
}
b, err := json.Marshal(data)
if err != nil {
return nil, err
}
return bytes.NewReader(b), nil
}
func ensureReaderClosed(response *http.Response) {
if response != nil && response.Body != nil {
// Drain up to 512 bytes and close the body to let the Transport reuse the connection
// see https://github.com/google/go-github/pull/317/files#r57536827
//
// TODO(thaJeztah): see if this optimization is still needed, or already implemented in stdlib,
// and check if context-cancellation should handle this as well. If still needed, consider
// wrapping response.Body, or returning a "closer()" from [Client.sendRequest] and related
// methods.
_, _ = io.CopyN(io.Discard, response.Body, 512)
_ = response.Body.Close()
if response == nil || response.Body == nil {
return
}
if response.ContentLength == 0 || (response.Request != nil && response.Request.Method == http.MethodHead) {
// No need to drain head requests or zero-length responses.
_ = response.Body.Close()
return
}
// Drain up to 512 bytes and close the body to let the Transport reuse the connection
// see https://github.com/google/go-github/pull/317/files#r57536827
//
// TODO(thaJeztah): see if this optimization is still needed, or already implemented in stdlib,
// and check if context-cancellation should handle this as well. If still needed, consider
// wrapping response.Body, or returning a "closer()" from [Client.sendRequest] and related
// methods.
_, _ = io.CopyN(io.Discard, response.Body, 512)
_ = response.Body.Close()
}

View File

@@ -7,7 +7,6 @@ import (
"io"
"math/rand"
"net/http"
"strings"
"testing"
"time"
@@ -64,7 +63,7 @@ func TestSetHostHeader(t *testing.T) {
}), WithHost(tc.host))
assert.NilError(t, err)
_, err = client.sendRequest(context.Background(), http.MethodGet, testEndpoint, nil, nil, nil)
_, err = client.sendRequest(t.Context(), http.MethodGet, testEndpoint, nil, nil, nil)
assert.NilError(t, err)
})
}
@@ -76,7 +75,7 @@ func TestSetHostHeader(t *testing.T) {
func TestPlainTextError(t *testing.T) {
client, err := New(WithMockClient(mockResponse(http.StatusInternalServerError, nil, "Server error")))
assert.NilError(t, err)
_, err = client.ContainerList(context.Background(), ContainerListOptions{})
_, err = client.ContainerList(t.Context(), ContainerListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -195,11 +194,11 @@ func TestResponseErrors(t *testing.T) {
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
return mockResponse(http.StatusBadRequest, http.Header{"Content-Type": []string{tc.contentType}}, tc.response)(req)
}))
if tc.apiVersion != "" {
client, err = New(WithHTTPClient(client.client), WithVersion(tc.apiVersion))
client, err = New(WithHTTPClient(client.client), WithAPIVersion(tc.apiVersion))
}
assert.NilError(t, err)
_, err = client.Ping(t.Context(), PingOptions{})
@@ -211,7 +210,7 @@ func TestResponseErrors(t *testing.T) {
func TestInfiniteError(t *testing.T) {
infinitR := rand.New(rand.NewSource(42))
client, err := New(WithMockClient(func(req *http.Request) (*http.Response, error) {
client, err := New(WithBaseMockClient(func(req *http.Request) (*http.Response, error) {
resp := &http.Response{
StatusCode: http.StatusInternalServerError,
Header: http.Header{},
@@ -235,7 +234,7 @@ func TestCanceledContext(t *testing.T) {
}))
assert.NilError(t, err)
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(t.Context())
cancel()
_, err = client.sendRequest(ctx, http.MethodGet, testEndpoint, nil, nil, nil)
@@ -251,7 +250,7 @@ func TestDeadlineExceededContext(t *testing.T) {
}))
assert.NilError(t, err)
ctx, cancel := context.WithDeadline(context.Background(), time.Now())
ctx, cancel := context.WithDeadline(t.Context(), time.Now())
defer cancel()
<-ctx.Done()
@@ -266,26 +265,25 @@ func TestPrepareJSONRequest(t *testing.T) {
body any
headers http.Header
expBody string
expNilBody bool
expHeaders http.Header
}{
{
doc: "nil body",
body: nil,
headers: http.Header{"Something": []string{"something"}},
expNilBody: true,
doc: "nil body",
body: nil,
headers: http.Header{"Something": []string{"something"}},
expBody: "",
expHeaders: http.Header{
// currently, no content-type is set on empty requests.
// no content-type is set on empty requests.
"Something": []string{"something"},
},
},
{
doc: "nil interface body",
body: (*struct{})(nil),
headers: http.Header{"Something": []string{"something"}},
expNilBody: true,
doc: "nil interface body",
body: (*struct{})(nil),
headers: http.Header{"Something": []string{"something"}},
expBody: "",
expHeaders: http.Header{
// currently, no content-type is set on empty requests.
// no content-type is set on empty requests.
"Something": []string{"something"},
},
},
@@ -308,12 +306,16 @@ func TestPrepareJSONRequest(t *testing.T) {
},
},
{
doc: "empty body",
body: http.NoBody,
expBody: `{}`,
expHeaders: http.Header{
"Content-Type": []string{"application/json"},
},
doc: "empty json raw message",
body: json.RawMessage(""),
expBody: "",
expHeaders: nil, // no content-type is set on empty requests.
},
{
doc: "empty body",
body: http.NoBody,
expBody: "",
expHeaders: nil, // no content-type is set on empty requests.
},
}
@@ -322,16 +324,9 @@ func TestPrepareJSONRequest(t *testing.T) {
req, hdr, err := prepareJSONRequest(tc.body, tc.headers)
assert.NilError(t, err)
var body string
if tc.expNilBody {
assert.Check(t, is.Nil(req))
} else {
assert.Assert(t, req != nil)
resp, err := io.ReadAll(req)
assert.NilError(t, err)
body = strings.TrimSpace(string(resp))
}
resp, err := io.ReadAll(req)
assert.NilError(t, err)
body := string(resp)
assert.Check(t, is.Equal(body, tc.expBody))
assert.Check(t, is.DeepEqual(hdr, tc.expHeaders))

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,7 +13,7 @@ import (
func TestSecretCreateError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SecretCreate(context.Background(), SecretCreateOptions{})
_, err = client.SecretCreate(t.Context(), SecretCreateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -30,7 +29,7 @@ func TestSecretCreate(t *testing.T) {
}))
assert.NilError(t, err)
r, err := client.SecretCreate(context.Background(), SecretCreateOptions{})
r, err := client.SecretCreate(t.Context(), SecretCreateOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(r.ID, "test_secret"))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"errors"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestSecretInspectError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SecretInspect(context.Background(), "nothing", SecretInspectOptions{})
_, err = client.SecretInspect(t.Context(), "nothing", SecretInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -24,7 +23,7 @@ func TestSecretInspectSecretNotFound(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusNotFound, "Server error")))
assert.NilError(t, err)
_, err = client.SecretInspect(context.Background(), "unknown", SecretInspectOptions{})
_, err = client.SecretInspect(t.Context(), "unknown", SecretInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -33,11 +32,11 @@ func TestSecretInspectWithEmptyID(t *testing.T) {
return nil, errors.New("should not make request")
}))
assert.NilError(t, err)
_, err = client.SecretInspect(context.Background(), "", SecretInspectOptions{})
_, err = client.SecretInspect(t.Context(), "", SecretInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.SecretInspect(context.Background(), " ", SecretInspectOptions{})
_, err = client.SecretInspect(t.Context(), " ", SecretInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -54,7 +53,7 @@ func TestSecretInspect(t *testing.T) {
}))
assert.NilError(t, err)
res, err := client.SecretInspect(context.Background(), "secret_id", SecretInspectOptions{})
res, err := client.SecretInspect(t.Context(), "secret_id", SecretInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(res.Secret.ID, "secret_id"))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestSecretListError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SecretList(context.Background(), SecretListOptions{})
_, err = client.SecretList(t.Context(), SecretListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -62,7 +61,7 @@ func TestSecretList(t *testing.T) {
}))
assert.NilError(t, err)
res, err := client.SecretList(context.Background(), listCase.options)
res, err := client.SecretList(t.Context(), listCase.options)
assert.NilError(t, err)
assert.Check(t, is.Len(res.Items, 2))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestSecretRemoveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SecretRemove(context.Background(), "secret_id", SecretRemoveOptions{})
_, err = client.SecretRemove(t.Context(), "secret_id", SecretRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.SecretRemove(context.Background(), "", SecretRemoveOptions{})
_, err = client.SecretRemove(t.Context(), "", SecretRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.SecretRemove(context.Background(), " ", SecretRemoveOptions{})
_, err = client.SecretRemove(t.Context(), " ", SecretRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestSecretRemove(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.SecretRemove(context.Background(), "secret_id", SecretRemoveOptions{})
_, err = client.SecretRemove(t.Context(), "secret_id", SecretRemoveOptions{})
assert.NilError(t, err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestSecretUpdateError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SecretUpdate(context.Background(), "secret_id", SecretUpdateOptions{})
_, err = client.SecretUpdate(t.Context(), "secret_id", SecretUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.SecretUpdate(context.Background(), "", SecretUpdateOptions{})
_, err = client.SecretUpdate(t.Context(), "", SecretUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.SecretUpdate(context.Background(), " ", SecretUpdateOptions{})
_, err = client.SecretUpdate(t.Context(), " ", SecretUpdateOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -37,6 +36,6 @@ func TestSecretUpdate(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.SecretUpdate(context.Background(), "secret_id", SecretUpdateOptions{})
_, err = client.SecretUpdate(t.Context(), "secret_id", SecretUpdateOptions{})
assert.NilError(t, err)
}

View File

@@ -59,16 +59,18 @@ func (cli *Client) ServiceCreate(ctx context.Context, options ServiceCreateOptio
options.Spec.TaskTemplate.ContainerSpec.Image = taggedImg
}
if options.QueryRegistry {
resolveWarning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
if warning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
warnings = append(warnings, warning)
}
}
case options.Spec.TaskTemplate.PluginSpec != nil:
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
options.Spec.TaskTemplate.PluginSpec.Remote = taggedImg
}
if options.QueryRegistry {
resolveWarning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
if warning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
warnings = append(warnings, warning)
}
}
}
@@ -93,35 +95,33 @@ func (cli *Client) ServiceCreate(ctx context.Context, options ServiceCreateOptio
}
func resolveContainerSpecImage(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
var warning string
if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.ContainerSpec.Image, encodedAuth); err != nil {
warning = digestWarning(taskSpec.ContainerSpec.Image)
} else {
taskSpec.ContainerSpec.Image = img
if len(imgPlatforms) > 0 {
if taskSpec.Placement == nil {
taskSpec.Placement = &swarm.Placement{}
}
taskSpec.Placement.Platforms = imgPlatforms
}
img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.ContainerSpec.Image, encodedAuth)
if err != nil {
return digestWarning(taskSpec.ContainerSpec.Image)
}
return warning
taskSpec.ContainerSpec.Image = img
if len(imgPlatforms) > 0 {
if taskSpec.Placement == nil {
taskSpec.Placement = &swarm.Placement{}
}
taskSpec.Placement.Platforms = imgPlatforms
}
return ""
}
func resolvePluginSpecRemote(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
var warning string
if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.PluginSpec.Remote, encodedAuth); err != nil {
warning = digestWarning(taskSpec.PluginSpec.Remote)
} else {
taskSpec.PluginSpec.Remote = img
if len(imgPlatforms) > 0 {
if taskSpec.Placement == nil {
taskSpec.Placement = &swarm.Placement{}
}
taskSpec.Placement.Platforms = imgPlatforms
}
img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.PluginSpec.Remote, encodedAuth)
if err != nil {
return digestWarning(taskSpec.PluginSpec.Remote)
}
return warning
taskSpec.PluginSpec.Remote = img
if len(imgPlatforms) > 0 {
if taskSpec.Placement == nil {
taskSpec.Placement = &swarm.Placement{}
}
taskSpec.Placement.Platforms = imgPlatforms
}
return ""
}
func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) {

View File

@@ -29,7 +29,7 @@ func TestServiceCreateError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestServiceCreateConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ServiceCreate(t.Context(), ServiceCreateOptions{})

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"errors"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestServiceInspectError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ServiceInspect(context.Background(), "nothing", ServiceInspectOptions{})
_, err = client.ServiceInspect(t.Context(), "nothing", ServiceInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -24,7 +23,7 @@ func TestServiceInspectServiceNotFound(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusNotFound, "Server error")))
assert.NilError(t, err)
_, err = client.ServiceInspect(context.Background(), "unknown", ServiceInspectOptions{})
_, err = client.ServiceInspect(t.Context(), "unknown", ServiceInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -33,11 +32,11 @@ func TestServiceInspectWithEmptyID(t *testing.T) {
return nil, errors.New("should not make request")
}))
assert.NilError(t, err)
_, err = client.ServiceInspect(context.Background(), "", ServiceInspectOptions{})
_, err = client.ServiceInspect(t.Context(), "", ServiceInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ServiceInspect(context.Background(), " ", ServiceInspectOptions{})
_, err = client.ServiceInspect(t.Context(), " ", ServiceInspectOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -54,7 +53,7 @@ func TestServiceInspect(t *testing.T) {
}))
assert.NilError(t, err)
inspect, err := client.ServiceInspect(context.Background(), "service_id", ServiceInspectOptions{})
inspect, err := client.ServiceInspect(t.Context(), "service_id", ServiceInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(inspect.Service.ID, "service_id"))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -16,7 +15,7 @@ func TestServiceListError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ServiceList(context.Background(), ServiceListOptions{})
_, err = client.ServiceList(t.Context(), ServiceListOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -62,7 +61,7 @@ func TestServiceList(t *testing.T) {
}))
assert.NilError(t, err)
list, err := client.ServiceList(context.Background(), listCase.options)
list, err := client.ServiceList(t.Context(), listCase.options)
assert.NilError(t, err)
assert.Check(t, is.Len(list.Items, 2))
}

View File

@@ -129,7 +129,7 @@ func TestServiceLogs(t *testing.T) {
}
func ExampleClient_ServiceLogs_withTimeout() {
client, err := New(FromEnv, WithAPIVersionNegotiation())
client, err := New(FromEnv)
if err != nil {
log.Fatal(err)
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,14 +13,14 @@ func TestServiceRemoveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.ServiceRemove(context.Background(), "service_id", ServiceRemoveOptions{})
_, err = client.ServiceRemove(t.Context(), "service_id", ServiceRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ServiceRemove(context.Background(), "", ServiceRemoveOptions{})
_, err = client.ServiceRemove(t.Context(), "", ServiceRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ServiceRemove(context.Background(), " ", ServiceRemoveOptions{})
_, err = client.ServiceRemove(t.Context(), " ", ServiceRemoveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
}
@@ -30,7 +29,7 @@ func TestServiceRemoveNotFoundError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusNotFound, "no such service: service_id")))
assert.NilError(t, err)
_, err = client.ServiceRemove(context.Background(), "service_id", ServiceRemoveOptions{})
_, err = client.ServiceRemove(t.Context(), "service_id", ServiceRemoveOptions{})
assert.Check(t, is.ErrorContains(err, "no such service: service_id"))
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
}
@@ -46,6 +45,6 @@ func TestServiceRemove(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.ServiceRemove(context.Background(), "service_id", ServiceRemoveOptions{})
_, err = client.ServiceRemove(t.Context(), "service_id", ServiceRemoveOptions{})
assert.NilError(t, err)
}

View File

@@ -82,16 +82,18 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, options
options.Spec.TaskTemplate.ContainerSpec.Image = taggedImg
}
if options.QueryRegistry {
resolveWarning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
if warning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
warnings = append(warnings, warning)
}
}
case options.Spec.TaskTemplate.PluginSpec != nil:
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
options.Spec.TaskTemplate.PluginSpec.Remote = taggedImg
}
if options.QueryRegistry {
resolveWarning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
if warning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
warnings = append(warnings, warning)
}
}
}

View File

@@ -32,7 +32,7 @@ func TestServiceUpdateError(t *testing.T) {
//
// Regression test for https://github.com/docker/cli/issues/4890
func TestServiceUpdateConnectionError(t *testing.T) {
client, err := New(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
client, err := New(WithHost("tcp://no-such-host.invalid"))
assert.NilError(t, err)
_, err = client.ServiceUpdate(t.Context(), "service_id", ServiceUpdateOptions{})

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -15,7 +14,7 @@ func TestSwarmGetUnlockKeyError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SwarmGetUnlockKey(context.Background())
_, err = client.SwarmGetUnlockKey(t.Context())
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -35,7 +34,7 @@ func TestSwarmGetUnlockKey(t *testing.T) {
}))
assert.NilError(t, err)
result, err := client.SwarmGetUnlockKey(context.Background())
result, err := client.SwarmGetUnlockKey(t.Context())
assert.NilError(t, err)
assert.Check(t, is.Equal(unlockKey, result.Key))
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,7 +13,7 @@ func TestSwarmInitError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SwarmInit(context.Background(), SwarmInitOptions{})
_, err = client.SwarmInit(t.Context(), SwarmInitOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -29,7 +28,7 @@ func TestSwarmInit(t *testing.T) {
}))
assert.NilError(t, err)
result, err := client.SwarmInit(context.Background(), SwarmInitOptions{
result, err := client.SwarmInit(t.Context(), SwarmInitOptions{
ListenAddr: "0.0.0.0:2377",
})
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,7 +13,7 @@ func TestSwarmJoinError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SwarmJoin(context.Background(), SwarmJoinOptions{})
_, err = client.SwarmJoin(t.Context(), SwarmJoinOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -29,7 +28,7 @@ func TestSwarmJoin(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.SwarmJoin(context.Background(), SwarmJoinOptions{
_, err = client.SwarmJoin(t.Context(), SwarmJoinOptions{
ListenAddr: "0.0.0.0:2377",
})
assert.NilError(t, err)

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"fmt"
"net/http"
"testing"
@@ -15,7 +14,7 @@ func TestSwarmLeaveError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SwarmLeave(context.Background(), SwarmLeaveOptions{})
_, err = client.SwarmLeave(t.Context(), SwarmLeaveOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -48,7 +47,7 @@ func TestSwarmLeave(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.SwarmLeave(context.Background(), SwarmLeaveOptions{Force: leaveCase.force})
_, err = client.SwarmLeave(t.Context(), SwarmLeaveOptions{Force: leaveCase.force})
assert.NilError(t, err)
}
}

View File

@@ -1,7 +1,6 @@
package client
import (
"context"
"net/http"
"testing"
@@ -14,7 +13,7 @@ func TestSwarmUnlockError(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.SwarmUnlock(context.Background(), SwarmUnlockOptions{Key: "SWMKEY-1-y6guTZNTwpQeTL5RhUfOsdBdXoQjiB2GADHSRJvbXeU"})
_, err = client.SwarmUnlock(t.Context(), SwarmUnlockOptions{Key: "SWMKEY-1-y6guTZNTwpQeTL5RhUfOsdBdXoQjiB2GADHSRJvbXeU"})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -29,6 +28,6 @@ func TestSwarmUnlock(t *testing.T) {
}))
assert.NilError(t, err)
_, err = client.SwarmUnlock(context.Background(), SwarmUnlockOptions{Key: "SWMKEY-1-y6guTZNTwpQeTL5RhUfOsdBdXoQjiB2GADHSRJvbXeU"})
_, err = client.SwarmUnlock(t.Context(), SwarmUnlockOptions{Key: "SWMKEY-1-y6guTZNTwpQeTL5RhUfOsdBdXoQjiB2GADHSRJvbXeU"})
assert.NilError(t, err)
}

View File

@@ -133,7 +133,7 @@ func TestLegacyDiskUsage(t *testing.T) {
const legacyVersion = "1.51"
const expectedURL = "/system/df"
client, err := New(
WithVersion(legacyVersion),
WithAPIVersion(legacyVersion),
WithMockClient(func(req *http.Request) (*http.Response, error) {
if err := assertRequest(req, http.MethodGet, "/v"+legacyVersion+expectedURL); err != nil {
return nil, err

View File

@@ -2,7 +2,6 @@ package client
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
@@ -37,8 +36,8 @@ func TestEventsErrorInOptions(t *testing.T) {
for _, tc := range errorCases {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
events := client.Events(context.Background(), tc.options)
err = <-events.Err
res := client.Events(t.Context(), tc.options)
err = <-res.Err
assert.Check(t, is.ErrorContains(err, tc.expectedError))
}
}
@@ -46,8 +45,8 @@ func TestEventsErrorInOptions(t *testing.T) {
func TestEventsErrorFromServer(t *testing.T) {
client, err := New(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
events := client.Events(context.Background(), EventsListOptions{})
err = <-events.Err
res := client.Events(t.Context(), EventsListOptions{})
err = <-res.Err
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -133,18 +132,18 @@ func TestEvents(t *testing.T) {
}))
assert.NilError(t, err)
events := client.Events(context.Background(), eventsCase.options)
res := client.Events(t.Context(), eventsCase.options)
loop:
for {
select {
case err := <-events.Err:
case err := <-res.Err:
if err != nil && !errors.Is(err, io.EOF) {
t.Fatal(err)
}
break loop
case e := <-events.Messages:
case e := <-res.Messages:
_, ok := eventsCase.expectedEvents[e.Actor.ID]
assert.Check(t, ok, "event received not expected with action %s & id %s", e.Action, e.Actor.ID)
}

Some files were not shown because too many files have changed in this diff Show More