Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-docker for openSUSE:Factory checked in at 2026-08-21 17:01:21 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-docker (Old) and /work/SRC/openSUSE:Factory/.python-docker.new.1258 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-docker" Fri Aug 21 17:01:21 2026 rev:30 rq:1372815 version:7.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-docker/python-docker.changes 2024-08-28 21:34:37.930609627 +0200 +++ /work/SRC/openSUSE:Factory/.python-docker.new.1258/python-docker.changes 2026-08-21 17:02:41.837975359 +0200 @@ -1,0 +2,59 @@ +Fri Aug 21 10:26:21 UTC 2026 - Dirk Müller <[email protected]> + +- update to 7.2.0: + * ## Upgrade Notes + * `docker.from_env()` now honors the active Docker CLI context + when `DOCKER_HOST` is not set. + * This means the client may connect to the daemon selected by + `DOCKER_CONTEXT` or the current context in + `~/.docker/config.json`, matching Docker CLI behavior more + closely. + * If your application relied on the previous default connection + behavior, set `DOCKER_HOST` explicitly, set + `DOCKER_CONTEXT=default`, or pass `use_context=False` to + `DockerClient.from_env()`. + * Added `docker.from_context()` / `DockerClient.from_context()` + for explicitly creating a client from a Docker CLI context. + * ## Features + * Added support for Docker contexts when creating the default + client + * Added subpath support for volumes + * ## Bugfixes + * Fixed `exec_run` documentation for the `stream` parameter + * Fixed image loading to avoid depending on the deprecated + `JSONMessage.error` field + * Preserved the rotated unlock key in swarm integration tests + * Fixed SSL certificate generation in tests + * Fixed IPv6 integration tests by explicitly enabling IPv6 + where required + * ## Miscellaneous + * Updated tests for newer Docker Engine behavior + * CI and build updates + * tests/exec: expect 127 exit code for missing executable + * fixing doc for stream param in exec_run + * Bump default API version to 1.45 (Moby 26.0/26.1) + * Set a dummy-version if none set, and remove unused APT_MIRROR + build-arg + * test_service_logs: stop testing experimental versions + * Makefile: fix circular reference for integration-dind + * image load: don't depend on deprecated JSONMessage.error + field + * integration: test_create_volume_invalid_driver allow either + 400 or 404 + * integration: adjust tests for omitted "OnBuild" + * Implement Subpath Support for Volumes in Docker-Py + * tests: fix ssl generation + * test/integration: don't check for deprecated Networks field + * test: Skip from_env_unix tests if DOCKER_HOST is network + socket + * test_connect_with_ipv6_address: enable IPv6 + * test_create_with_ipv6_address: enable IPv6 + * tests: Migrate off gpg2 and regenerate key ed25519 + * gha: Pin to digests + * Fix integration tests on non-amd64 hosts and add ARM64 CI + * integration/swarm: Preserve rotated unlock key + * [DKP-2535] Honour context if present for default client, add + contexts support + * docs: 7.2.0 changelog + +------------------------------------------------------------------- Old: ---- docker-7.1.0.tar.gz New: ---- docker-7.2.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-docker.spec ++++++ --- /var/tmp/diff_new_pack.sY6lxg/_old 2026-08-21 17:02:42.768008287 +0200 +++ /var/tmp/diff_new_pack.sY6lxg/_new 2026-08-21 17:02:42.770008358 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-docker # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-docker -Version: 7.1.0 +Version: 7.2.0 Release: 0 Summary: A Python library for the Docker Engine API License: Apache-2.0 ++++++ docker-7.1.0.tar.gz -> docker-7.2.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/.github/workflows/ci.yml new/docker-py-7.2.0/.github/workflows/ci.yml --- old/docker-py-7.1.0/.github/workflows/ci.yml 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/.github/workflows/ci.yml 2026-07-09 15:51:20.000000000 +0200 @@ -10,8 +10,8 @@ lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: '3.x' - run: pip install -U ruff==0.1.8 @@ -21,12 +21,12 @@ build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: '3.x' - run: pip3 install build && python -m build . - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: dist path: dist @@ -38,9 +38,9 @@ python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ matrix.python-version }} allow-prereleases: true @@ -61,7 +61,7 @@ variant: [ "integration-dind", "integration-dind-ssl", "integration-dind-ssh" ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 fetch-tags: true @@ -70,3 +70,16 @@ docker logout rm -rf ~/.docker make ${{ matrix.variant }} + + arm64-integration-tests: + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + fetch-depth: 0 + fetch-tags: true + - name: make integration-dind + run: | + docker logout + rm -rf ~/.docker + make integration-dind diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/.github/workflows/release.yml new/docker-py-7.2.0/.github/workflows/release.yml --- old/docker-py-7.1.0/.github/workflows/release.yml 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/.github/workflows/release.yml 2026-07-09 15:51:20.000000000 +0200 @@ -20,9 +20,9 @@ publish: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: '3.x' @@ -36,13 +36,13 @@ SETUPTOOLS_SCM_PRETEND_VERSION: ${{ inputs.tag }} - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 if: '! inputs.dry-run' with: password: ${{ secrets.PYPI_API_TOKEN }} - name: Create GitHub release - uses: ncipollo/release-action@v1 + uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0 if: '! inputs.dry-run' with: artifacts: "dist/*" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/Dockerfile new/docker-py-7.2.0/Dockerfile --- old/docker-py-7.1.0/Dockerfile 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/Dockerfile 2026-07-09 15:51:20.000000000 +0200 @@ -6,7 +6,7 @@ WORKDIR /src COPY . . -ARG VERSION +ARG VERSION=0.0.0.dev0 RUN --mount=type=cache,target=/cache/pip \ PIP_CACHE_DIR=/cache/pip \ SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION} \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/Dockerfile-docs new/docker-py-7.2.0/Dockerfile-docs --- old/docker-py-7.1.0/Dockerfile-docs 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/Dockerfile-docs 2026-07-09 15:51:20.000000000 +0200 @@ -13,7 +13,7 @@ WORKDIR /src COPY . . -ARG VERSION +ARG VERSION=0.0.0.dev0 RUN --mount=type=cache,target=/cache/pip \ PIP_CACHE_DIR=/cache/pip \ SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION} \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/Makefile new/docker-py-7.2.0/Makefile --- old/docker-py-7.1.0/Makefile 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/Makefile 2026-07-09 15:51:20.000000000 +0200 @@ -1,5 +1,5 @@ -TEST_API_VERSION ?= 1.44 -TEST_ENGINE_VERSION ?= 25.0 +TEST_API_VERSION ?= 1.45 +TEST_ENGINE_VERSION ?= 26.1 ifeq ($(OS),Windows_NT) PLATFORM := Windows @@ -13,7 +13,7 @@ SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER ?= $(shell git describe --match '[0-9]*' --dirty='.m' --always --tags 2>/dev/null | sed -r 's/-([0-9]+)/.dev\1/' | sed 's/-/+/') ifeq ($(SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER),) - SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER = "dev" + SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER = "0.0.0.dev0" endif .PHONY: all @@ -33,7 +33,7 @@ --build-arg VERSION=${SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER} \ --build-arg ENGINE_VERSION=${TEST_ENGINE_VERSION} \ --build-arg API_VERSION=${TEST_API_VERSION} \ - --build-arg APT_MIRROR . + . .PHONY: build build: @@ -42,7 +42,7 @@ -t docker-sdk-python3 \ -f tests/Dockerfile \ --build-arg VERSION=${SETUPTOOLS_SCM_PRETEND_VERSION_DOCKER} \ - --build-arg APT_MIRROR . + . .PHONY: build-docs build-docs: @@ -77,9 +77,6 @@ docker network inspect dpy-tests || docker network create dpy-tests .PHONY: integration-dind -integration-dind: integration-dind - -.PHONY: integration-dind integration-dind: build setup-network docker rm -vf dpy-dind || : diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/docker/__init__.py new/docker-py-7.2.0/docker/__init__.py --- old/docker-py-7.1.0/docker/__init__.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/docker/__init__.py 2026-07-09 15:51:20.000000000 +0200 @@ -1,5 +1,5 @@ from .api import APIClient -from .client import DockerClient, from_env +from .client import DockerClient, from_context, from_env from .context import Context, ContextAPI from .tls import TLSConfig from .version import __version__ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/docker/client.py new/docker-py-7.2.0/docker/client.py --- old/docker-py-7.1.0/docker/client.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/docker/client.py 2026-07-09 15:51:20.000000000 +0200 @@ -1,5 +1,8 @@ +import os + from .api.client import APIClient from .constants import DEFAULT_MAX_POOL_SIZE, DEFAULT_TIMEOUT_SECONDS +from .context import ContextAPI from .models.configs import ConfigCollection from .models.containers import ContainerCollection from .models.images import ImageCollection @@ -78,6 +81,10 @@ use_ssh_client (bool): If set to `True`, an ssh connection is made via shelling out to the ssh client. Ensure the ssh client is installed and configured on the host. + use_context (bool): If ``True`` (the default), fall back to the + current Docker CLI context (``~/.docker/config.json`` / + ``DOCKER_CONTEXT``) when ``DOCKER_HOST`` is not set. This + allows the client to talk to Docker Desktop out of the box. Example: @@ -91,12 +98,66 @@ max_pool_size = kwargs.pop('max_pool_size', DEFAULT_MAX_POOL_SIZE) version = kwargs.pop('version', None) use_ssh_client = kwargs.pop('use_ssh_client', False) + use_context = kwargs.pop('use_context', True) + environment = kwargs.get('environment') or os.environ + + params = kwargs_from_env(**kwargs) + if use_context and 'base_url' not in params: + for k, v in ContextAPI.kwargs_from_context( + environment=environment).items(): + params.setdefault(k, v) + + return cls( + timeout=timeout, + max_pool_size=max_pool_size, + version=version, + use_ssh_client=use_ssh_client, + **params, + ) + + @classmethod + def from_context(cls, name=None, **kwargs): + """ + Return a client configured from a Docker CLI context. + + With no ``name``, resolves the current context the same way the + Docker CLI does: ``DOCKER_CONTEXT`` env var, then the + ``currentContext`` field in ``~/.docker/config.json``, falling back + to the built-in ``default`` context. On a machine with Docker + Desktop installed this typically resolves to ``desktop-linux``. + + Args: + name (str): Name of the context to load. ``None`` (the default) + means "use the current context". + version (str): The version of the API to use. Set to ``auto`` to + automatically detect the server's version. + timeout (int): Default timeout for API calls, in seconds. + max_pool_size (int): The maximum number of connections to save in + the pool. + use_ssh_client (bool): If ``True``, shell out to the ssh client + for ssh:// contexts. + + Example: + + >>> import docker + >>> client = docker.DockerClient.from_context() + >>> # or, pick a specific context: + >>> client = docker.DockerClient.from_context('desktop-linux') + """ + timeout = kwargs.pop('timeout', DEFAULT_TIMEOUT_SECONDS) + max_pool_size = kwargs.pop('max_pool_size', DEFAULT_MAX_POOL_SIZE) + version = kwargs.pop('version', None) + use_ssh_client = kwargs.pop('use_ssh_client', False) + + params = ContextAPI.kwargs_from_context(name=name) + params.update(kwargs) + return cls( timeout=timeout, max_pool_size=max_pool_size, version=version, use_ssh_client=use_ssh_client, - **kwargs_from_env(**kwargs) + **params, ) # Resources @@ -220,3 +281,4 @@ from_env = DockerClient.from_env +from_context = DockerClient.from_context diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/docker/constants.py new/docker-py-7.2.0/docker/constants.py --- old/docker-py-7.1.0/docker/constants.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/docker/constants.py 2026-07-09 15:51:20.000000000 +0200 @@ -2,7 +2,7 @@ from .version import __version__ -DEFAULT_DOCKER_API_VERSION = '1.44' +DEFAULT_DOCKER_API_VERSION = '1.45' MINIMUM_DOCKER_API_VERSION = '1.24' DEFAULT_TIMEOUT_SECONDS = 60 STREAM_HEADER_SIZE_BYTES = 8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/docker/context/api.py new/docker-py-7.2.0/docker/context/api.py --- old/docker-py-7.1.0/docker/context/api.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/docker/context/api.py 2026-07-09 15:51:20.000000000 +0200 @@ -133,6 +133,33 @@ return cls.get_context() @classmethod + def kwargs_from_context(cls, name=None, environment=None): + """Build ``base_url`` / ``tls`` kwargs from a Docker CLI context. + + Mirrors the Docker CLI: if ``name`` is not given, honours the + ``DOCKER_CONTEXT`` env var, then the ``currentContext`` field in + ``~/.docker/config.json``, defaulting to the built-in ``default`` + context (local socket / named pipe). On a host with Docker Desktop + this resolves to the ``desktop-linux`` (or equivalent) context, so + client construction targets Docker Desktop out of the box. + """ + if environment is None: + environment = os.environ + if name is None: + name = environment.get("DOCKER_CONTEXT") + ctx = cls.get_context(name) + if ctx is None: + return {} + host = ctx.Host + if not host: + return {} + params = {"base_url": host} + tls_cfg = ctx.TLSConfig + if tls_cfg is not None: + params["tls"] = tls_cfg + return params + + @classmethod def set_current_context(cls, name="default"): ctx = cls.get_context(name) if not ctx: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/docker/models/containers.py new/docker-py-7.2.0/docker/models/containers.py --- old/docker-py-7.1.0/docker/models/containers.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/docker/models/containers.py 2026-07-09 15:51:20.000000000 +0200 @@ -181,7 +181,8 @@ user (str): User to execute command as. Default: root detach (bool): If true, detach from the exec command. Default: False - stream (bool): Stream response data. Default: False + stream (bool): Stream response data. Ignored if ``detach`` is true. + Default: False socket (bool): Return the connection socket to allow custom read/write operations. Default: False environment (dict or list): A dictionary or a list of strings in diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/docker/models/images.py new/docker-py-7.2.0/docker/models/images.py --- old/docker-py-7.1.0/docker/models/images.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/docker/models/images.py 2026-07-09 15:51:20.000000000 +0200 @@ -407,8 +407,8 @@ if match: image_id = match.group(2) images.append(image_id) - if 'error' in chunk: - raise ImageLoadError(chunk['error']) + if 'errorDetail' in chunk: + raise ImageLoadError(chunk['errorDetail']['message']) return [self.get(i) for i in images] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/docker/types/services.py new/docker-py-7.2.0/docker/types/services.py --- old/docker-py-7.1.0/docker/types/services.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/docker/types/services.py 2026-07-09 15:51:20.000000000 +0200 @@ -242,6 +242,7 @@ for the ``volume`` type. driver_config (DriverConfig): Volume driver configuration. Only valid for the ``volume`` type. + subpath (str): Path inside a volume to mount instead of the volume root. tmpfs_size (int or string): The size for the tmpfs mount in bytes. tmpfs_mode (int): The permission mode for the tmpfs mount. """ @@ -249,7 +250,7 @@ def __init__(self, target, source, type='volume', read_only=False, consistency=None, propagation=None, no_copy=False, labels=None, driver_config=None, tmpfs_size=None, - tmpfs_mode=None): + tmpfs_mode=None, subpath=None): self['Target'] = target self['Source'] = source if type not in ('bind', 'volume', 'tmpfs', 'npipe'): @@ -267,7 +268,7 @@ self['BindOptions'] = { 'Propagation': propagation } - if any([labels, driver_config, no_copy, tmpfs_size, tmpfs_mode]): + if any([labels, driver_config, no_copy, tmpfs_size, tmpfs_mode, subpath]): raise errors.InvalidArgument( 'Incompatible options have been provided for the bind ' 'type mount.' @@ -280,6 +281,8 @@ volume_opts['Labels'] = labels if driver_config: volume_opts['DriverConfig'] = driver_config + if subpath: + volume_opts['Subpath'] = subpath if volume_opts: self['VolumeOptions'] = volume_opts if any([propagation, tmpfs_size, tmpfs_mode]): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/docs/change-log.md new/docker-py-7.2.0/docs/change-log.md --- old/docker-py-7.1.0/docs/change-log.md 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/docs/change-log.md 2026-07-09 15:51:20.000000000 +0200 @@ -1,6 +1,29 @@ Changelog ========== +7.2.0 +----- +### Upgrade Notes +- `docker.from_env()` now honors the active Docker CLI context when `DOCKER_HOST` is not set. + - This means the client may connect to the daemon selected by `DOCKER_CONTEXT` or the current context in `~/.docker/config.json`, matching Docker CLI behavior more closely. + - If your application relied on the previous default connection behavior, set `DOCKER_HOST` explicitly, set `DOCKER_CONTEXT=default`, or pass `use_context=False` to `DockerClient.from_env()`. +- Added `docker.from_context()` / `DockerClient.from_context()` for explicitly creating a client from a Docker CLI context. + +### Features +- Added support for Docker contexts when creating the default client +- Added subpath support for volumes + +### Bugfixes +- Fixed `exec_run` documentation for the `stream` parameter +- Fixed image loading to avoid depending on the deprecated `JSONMessage.error` field +- Preserved the rotated unlock key in swarm integration tests +- Fixed SSL certificate generation in tests +- Fixed IPv6 integration tests by explicitly enabling IPv6 where required + +## Miscellaneous +- Updated tests for newer Docker Engine behavior +- CI and build updates + 7.1.0 ----- ### Upgrade Notes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/Dockerfile new/docker-py-7.2.0/tests/Dockerfile --- old/docker-py-7.1.0/tests/Dockerfile 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/Dockerfile 2026-07-09 15:51:20.000000000 +0200 @@ -4,7 +4,7 @@ FROM python:${PYTHON_VERSION} RUN apt-get update && apt-get -y install --no-install-recommends \ - gnupg2 \ + gnupg \ pass # Add SSH keys and set permissions @@ -14,10 +14,10 @@ RUN chmod -R 600 /root/.ssh COPY ./tests/gpg-keys /gpg-keys -RUN gpg2 --import gpg-keys/secret -RUN gpg2 --import-ownertrust gpg-keys/ownertrust -RUN yes | pass init $(gpg2 --no-auto-check-trustdb --list-secret-key | awk '/^sec/{getline; $1=$1; print}') -RUN gpg2 --check-trustdb +RUN gpg --batch --pinentry-mode loopback --passphrase '' --import gpg-keys/secret +RUN gpg --import-ownertrust gpg-keys/ownertrust +RUN yes | pass init $(gpg --no-auto-check-trustdb --list-secret-key | awk '/^sec/{getline; $1=$1; print}') +RUN gpg --check-trustdb ARG CREDSTORE_VERSION=v0.6.3 RUN curl -sSL -o /opt/docker-credential-pass.tar.gz \ https://github.com/docker/docker-credential-helpers/releases/download/$CREDSTORE_VERSION/docker-credential-pass-$CREDSTORE_VERSION-amd64.tar.gz && \ @@ -28,7 +28,7 @@ WORKDIR /src COPY . . -ARG VERSION +ARG VERSION=0.0.0.dev0 RUN --mount=type=cache,target=/cache/pip \ PIP_CACHE_DIR=/cache/pip \ SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION} \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/Dockerfile-dind-certs new/docker-py-7.2.0/tests/Dockerfile-dind-certs --- old/docker-py-7.1.0/tests/Dockerfile-dind-certs 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/Dockerfile-dind-certs 2026-07-09 15:51:20.000000000 +0200 @@ -7,17 +7,46 @@ VOLUME /certs WORKDIR /tmp/certs + +# ---- CA (with proper v3_ca) ---- RUN openssl genrsa -aes256 -passout pass:foobar -out ca-key.pem 4096 -RUN echo "[req]\nprompt=no\ndistinguished_name = req_distinguished_name\n[req_distinguished_name]\ncountryName=AU" > /tmp/config -RUN openssl req -new -x509 -passin pass:foobar -config /tmp/config -days 365 -key ca-key.pem -sha256 -out ca.pem -RUN openssl genrsa -out server-key.pem -passout pass:foobar 4096 +COPY <<'EOF' /tmp/ca.cnf +[req] +prompt = no +distinguished_name = req_distinguished_name +x509_extensions = v3_ca + +[req_distinguished_name] +countryName = AU + +[v3_ca] +basicConstraints = critical, CA:TRUE +keyUsage = critical, keyCertSign, cRLSign +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +EOF +RUN openssl req -new -x509 -passin pass:foobar -config /tmp/ca.cnf -days 365 -key ca-key.pem -sha256 -out ca.pem + +# ---- Server cert (SAN + KU/EKU) ---- +RUN openssl genrsa -out server-key.pem 4096 RUN openssl req -subj "/CN=docker" -sha256 -new -key server-key.pem -out server.csr -RUN echo subjectAltName = DNS:docker,DNS:localhost > extfile.cnf -RUN openssl x509 -req -days 365 -passin pass:foobar -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf +COPY <<'EOF' /tmp/server-ext.cnf +basicConstraints = CA:FALSE +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +subjectAltName = DNS:docker, DNS:localhost +EOF +RUN openssl x509 -req -days 365 -passin pass:foobar -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile /tmp/server-ext.cnf + +# ---- Client cert (KU/EKU) ---- RUN openssl genrsa -out key.pem 4096 RUN openssl req -passin pass:foobar -subj '/CN=client' -new -key key.pem -out client.csr -RUN echo extendedKeyUsage = clientAuth > extfile.cnf -RUN openssl x509 -req -passin pass:foobar -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile.cnf +COPY <<'EOF' /tmp/client-ext.cnf +basicConstraints = CA:FALSE +keyUsage = critical, digitalSignature +extendedKeyUsage = clientAuth +EOF +RUN openssl x509 -req -passin pass:foobar -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile /tmp/client-ext.cnf RUN chmod -v 0400 ca-key.pem key.pem server-key.pem RUN chmod -v 0444 ca.pem server-cert.pem cert.pem diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/Dockerfile-ssh-dind new/docker-py-7.2.0/tests/Dockerfile-ssh-dind --- old/docker-py-7.1.0/tests/Dockerfile-ssh-dind 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/Dockerfile-ssh-dind 2026-07-09 15:51:20.000000000 +0200 @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 -ARG API_VERSION=1.44 -ARG ENGINE_VERSION=25.0 +ARG API_VERSION=1.45 +ARG ENGINE_VERSION=26.1 FROM docker:${ENGINE_VERSION}-dind diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/gpg-keys/ownertrust new/docker-py-7.2.0/tests/gpg-keys/ownertrust --- old/docker-py-7.1.0/tests/gpg-keys/ownertrust 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/gpg-keys/ownertrust 2026-07-09 15:51:20.000000000 +0200 @@ -1,3 +1,3 @@ -# List of assigned trustvalues, created Wed 25 Apr 2018 01:28:17 PM PDT +# List of assigned trustvalues, created Wed May 20 14:53:47 2026 UTC # (Use "gpg --import-ownertrust" to restore them) -9781B87DAB042E6FD51388A5464ED987A7B21401:6: +713D05DCAFD74767847E4BBBE596CB09C46DE21F:6: Binary files old/docker-py-7.1.0/tests/gpg-keys/secret and new/docker-py-7.2.0/tests/gpg-keys/secret differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/integration/api_build_test.py new/docker-py-7.2.0/tests/integration/api_build_test.py --- old/docker-py-7.1.0/tests/integration/api_build_test.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/integration/api_build_test.py 2026-07-09 15:51:20.000000000 +0200 @@ -275,7 +275,7 @@ pass info = self.client.inspect_image('build1') - assert not info['Config']['OnBuild'] + assert 'OnBuild' not in info['Config'] or not info['Config']['OnBuild'] @requires_api_version('1.25') def test_build_with_network_mode(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/integration/api_container_test.py new/docker-py-7.2.0/tests/integration/api_container_test.py --- old/docker-py-7.1.0/tests/integration/api_container_test.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/integration/api_container_test.py 2026-07-09 15:51:20.000000000 +0200 @@ -620,6 +620,56 @@ assert mount['Source'] == mount_data['Name'] assert mount_data['RW'] is True + @requires_api_version('1.45') + def test_create_with_subpath_volume_mount(self): + source_volume = helpers.random_name() + self.client.create_volume(name=source_volume) + + setup_container = None + test_container = None + + + # Create a file structure in the volume to test with + setup_container = self.client.create_container( + TEST_IMG, + [ + "sh", + "-c", + 'mkdir -p /vol/subdir && echo "test content" > /vol/subdir/testfile.txt', + ], + host_config=self.client.create_host_config( + binds=[f"{source_volume}:/vol"] + ), + ) + self.client.start(setup_container) + self.client.wait(setup_container) + + # Now test with subpath + mount = docker.types.Mount( + type="volume", + source=source_volume, + target=self.mount_dest, + read_only=True, + subpath="subdir", + ) + + + host_config = self.client.create_host_config(mounts=[mount]) + test_container = self.client.create_container( + TEST_IMG, + ["cat", os.path.join(self.mount_dest, "testfile.txt")], + host_config=host_config, + ) + + self.client.start(test_container) + self.client.wait(test_container) # Wait for container to finish + output = self.client.logs(test_container).decode("utf-8").strip() + + # If the subpath feature is working, we should be able to see the content + # of the file in the subdir + assert output == "test content" + + def check_container_data(self, inspect_data, rw, propagation='rprivate'): assert 'Mounts' in inspect_data filtered = list(filter( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/integration/api_network_test.py new/docker-py-7.2.0/tests/integration/api_network_test.py --- old/docker-py-7.1.0/tests/integration/api_network_test.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/integration/api_network_test.py 2026-07-09 15:51:20.000000000 +0200 @@ -232,6 +232,7 @@ @requires_api_version('1.22') def test_create_with_ipv6_address(self): net_name, net_id = self.create_network( + enable_ipv6=True, ipam=IPAMConfig( driver='default', pool_configs=[IPAMPool(subnet="2001:389::/64")], @@ -384,6 +385,7 @@ @requires_api_version('1.22') def test_connect_with_ipv6_address(self): net_name, net_id = self.create_network( + enable_ipv6=True, ipam=IPAMConfig( driver='default', pool_configs=[ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/integration/api_plugin_test.py new/docker-py-7.2.0/tests/integration/api_plugin_test.py --- old/docker-py-7.1.0/tests/integration/api_plugin_test.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/integration/api_plugin_test.py 2026-07-09 15:51:20.000000000 +0200 @@ -1,4 +1,5 @@ import os +import platform import pytest @@ -11,6 +12,10 @@ @requires_api_version('1.25') [email protected]( + platform.machine().lower() not in ('amd64', 'x86_64'), + reason='vieux/sshfs plugin is only supported on amd64', +) class PluginTest(BaseAPIIntegrationTest): @classmethod def teardown_class(cls): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/integration/api_service_test.py new/docker-py-7.2.0/tests/integration/api_service_test.py --- old/docker-py-7.1.0/tests/integration/api_service_test.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/integration/api_service_test.py 2026-07-09 15:51:20.000000000 +0200 @@ -5,7 +5,7 @@ import docker -from ..helpers import force_leave_swarm, requires_api_version, requires_experimental +from ..helpers import force_leave_swarm, requires_api_version from .base import TEST_IMG, BaseAPIIntegrationTest @@ -140,8 +140,7 @@ assert len(services) == 1 assert services[0]['ID'] == svc_id['ID'] - @requires_api_version('1.25') - @requires_experimental(until='1.29') + @requires_api_version('1.29') def test_service_logs(self): name, svc_id = self.create_simple_service() assert self.get_service_container(name, include_stopped=True) @@ -368,16 +367,18 @@ ) self.tmp_networks.append(net2['Id']) container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) - task_tmpl = docker.types.TaskTemplate(container_spec) - name = self.get_service_name() - svc_id = self.client.create_service( - task_tmpl, name=name, networks=[ + task_tmpl = docker.types.TaskTemplate( + container_spec, networks=[ 'dockerpytest_1', {'Target': 'dockerpytest_2'} ] ) + name = self.get_service_name() + svc_id = self.client.create_service( + task_tmpl, name=name + ) svc_info = self.client.inspect_service(svc_id) - assert 'Networks' in svc_info['Spec'] - assert svc_info['Spec']['Networks'] == [ + assert 'Networks' in svc_info['Spec']['TaskTemplate'] + assert svc_info['Spec']['TaskTemplate']['Networks'] == [ {'Target': net1['Id']}, {'Target': net2['Id']} ] @@ -1117,16 +1118,18 @@ ) self.tmp_networks.append(net2['Id']) container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) - task_tmpl = docker.types.TaskTemplate(container_spec) - name = self.get_service_name() - svc_id = self.client.create_service( - task_tmpl, name=name, networks=[ + task_tmpl = docker.types.TaskTemplate( + container_spec, networks=[ 'dockerpytest_1', {'Target': 'dockerpytest_2'} ] ) + name = self.get_service_name() + svc_id = self.client.create_service( + task_tmpl, name=name + ) svc_info = self.client.inspect_service(svc_id) - assert 'Networks' in svc_info['Spec'] - assert svc_info['Spec']['Networks'] == [ + assert 'Networks' in svc_info['Spec']['TaskTemplate'] + assert svc_info['Spec']['TaskTemplate']['Networks'] == [ {'Target': net1['Id']}, {'Target': net2['Id']} ] @@ -1144,8 +1147,11 @@ {'Target': net1['Id']}, {'Target': net2['Id']} ] + task_tmpl = docker.types.TaskTemplate( + container_spec, networks=[net1['Id']] + ) self._update_service( - svc_id, name, new_index, networks=[net1['Id']], + svc_id, name, new_index, task_tmpl, fetch_current_spec=True ) svc_info = self.client.inspect_service(svc_id) @@ -1314,7 +1320,6 @@ container_spec = docker.types.ContainerSpec( 'busybox', ['echo', 'hello'] ) - task_tmpl = docker.types.TaskTemplate(container_spec) net1 = self.client.create_network( self.get_service_name(), driver='overlay', ipam={'Driver': 'default'} @@ -1325,22 +1330,27 @@ ipam={'Driver': 'default'} ) self.tmp_networks.append(net2['Id']) + task_tmpl = docker.types.TaskTemplate( + container_spec, networks=[net1['Id']] + ) name = self.get_service_name() svc_id = self.client.create_service( - task_tmpl, name=name, networks=[net1['Id']] + task_tmpl, name=name ) svc_info = self.client.inspect_service(svc_id) - assert 'Networks' in svc_info['Spec'] - assert len(svc_info['Spec']['Networks']) > 0 - assert svc_info['Spec']['Networks'][0]['Target'] == net1['Id'] + assert 'Networks' in svc_info['Spec']['TaskTemplate'] + assert len(svc_info['Spec']['TaskTemplate']['Networks']) > 0 + assert svc_info['Spec']['TaskTemplate']['Networks'][0]['Target'] == net1['Id'] svc_info = self.client.inspect_service(svc_id) version_index = svc_info['Version']['Index'] - task_tmpl = docker.types.TaskTemplate(container_spec) + task_tmpl = docker.types.TaskTemplate( + container_spec, networks=[net2['Id']] + ) self._update_service( svc_id, name, version_index, task_tmpl, name=name, - networks=[net2['Id']], fetch_current_spec=True + fetch_current_spec=True ) svc_info = self.client.inspect_service(svc_id) task_template = svc_info['Spec']['TaskTemplate'] @@ -1352,8 +1362,11 @@ new_index = svc_info['Version']['Index'] assert new_index > version_index + task_tmpl = docker.types.TaskTemplate( + container_spec, networks=[net1['Id']] + ) self._update_service( - svc_id, name, new_index, name=name, networks=[net1['Id']], + svc_id, name, new_index, task_tmpl, name=name, fetch_current_spec=True ) svc_info = self.client.inspect_service(svc_id) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/integration/api_swarm_test.py new/docker-py-7.2.0/tests/integration/api_swarm_test.py --- old/docker-py-7.1.0/tests/integration/api_swarm_test.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/integration/api_swarm_test.py 2026-07-09 15:51:20.000000000 +0200 @@ -249,6 +249,7 @@ rotate_manager_unlock_key=True ) key_2 = self.client.get_unlock_key() + self._unlock_key = key_2 assert key_1['UnlockKey'] != key_2['UnlockKey'] @requires_api_version('1.30') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/integration/api_volume_test.py new/docker-py-7.2.0/tests/integration/api_volume_test.py --- old/docker-py-7.1.0/tests/integration/api_volume_test.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/integration/api_volume_test.py 2026-07-09 15:51:20.000000000 +0200 @@ -17,10 +17,16 @@ assert result['Driver'] == 'local' def test_create_volume_invalid_driver(self): - driver_name = 'invalid.driver' + # special name to avoid exponential timeout loop + # https://github.com/moby/moby/blob/9e00a63d65434cdedc444e79a2b33a7c202b10d8/pkg/plugins/client.go#L253-L254 + driver_name = 'this-plugin-does-not-exist' - with pytest.raises(docker.errors.NotFound): + with pytest.raises(docker.errors.APIError) as cm: self.client.create_volume('perfectcherryblossom', driver_name) + assert ( + cm.value.response.status_code == 404 or + cm.value.response.status_code == 400 + ) def test_list_volumes(self): name = 'imperishablenight' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/integration/credentials/create_gpg_key.sh new/docker-py-7.2.0/tests/integration/credentials/create_gpg_key.sh --- old/docker-py-7.1.0/tests/integration/credentials/create_gpg_key.sh 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/integration/credentials/create_gpg_key.sh 2026-07-09 15:51:20.000000000 +0200 @@ -1,12 +1,4 @@ #!/usr/bin/sh -haveged -gpg --batch --gen-key <<-EOF -%echo Generating a standard key -Key-Type: DSA -Key-Length: 1024 -Subkey-Type: ELG-E -Subkey-Length: 1024 -Name-Real: Sakuya Izayoi -Name-Email: [email protected] -Expire-Date: 0 -EOF \ No newline at end of file +gpg --batch --passphrase '' --quick-gen-key 'Example User <[email protected]>' ed25519 cert 0 +FINGERPRINT=$(gpg --no-auto-check-trustdb --list-secret-keys --with-colons | awk -F: '/^fpr/{print $10; exit}') +gpg --batch --passphrase '' --quick-add-key "$FINGERPRINT" cv25519 encr 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/integration/credentials/store_test.py new/docker-py-7.2.0/tests/integration/credentials/store_test.py --- old/docker-py-7.1.0/tests/integration/credentials/store_test.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/integration/credentials/store_test.py 2026-07-09 15:51:20.000000000 +0200 @@ -1,4 +1,5 @@ import os +import platform import random import shutil import sys @@ -14,6 +15,10 @@ ) [email protected]( + platform.machine().lower() not in ('amd64', 'x86_64'), + reason='docker-credential-pass test helper is only supported on amd64', +) class TestStore: def teardown_method(self): for server in self.tmp_keys: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/integration/models_containers_test.py new/docker-py-7.2.0/tests/integration/models_containers_test.py --- old/docker-py-7.1.0/tests/integration/models_containers_test.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/integration/models_containers_test.py 2026-07-09 15:51:20.000000000 +0200 @@ -131,10 +131,9 @@ assert 'NetworkSettings' in attrs assert 'Networks' in attrs['NetworkSettings'] assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name] - # Expect Aliases to list 'test_alias' and the container's short-id. - # In API version 1.45, the short-id will be removed. + # Aliases no longer include the container's short-id in API v1.45. assert attrs['NetworkSettings']['Networks'][net_name]['Aliases'] \ - == [test_alias, attrs['Id'][:12]] + == [test_alias] assert attrs['NetworkSettings']['Networks'][net_name]['DriverOpts'] \ == test_driver_opt @@ -191,9 +190,9 @@ assert 'NetworkSettings' in attrs assert 'Networks' in attrs['NetworkSettings'] assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name] - # Aliases should include the container's short-id (but it will be removed - # in API v1.45). - assert attrs['NetworkSettings']['Networks'][net_name]['Aliases'] == [attrs["Id"][:12]] + # Aliases no longer include the container's short-id in API v1.45. + assert (attrs['NetworkSettings']['Networks'][net_name]['Aliases'] + is None) assert (attrs['NetworkSettings']['Networks'][net_name]['DriverOpts'] is None) @@ -353,14 +352,26 @@ assert exec_output[0] == 0 assert exec_output[1] == b"hello\n" + def test_exec_run_error_code_from_exec(self): + client = docker.from_env(version=TEST_API_VERSION) + container = client.containers.run( + "alpine", "sh -c 'sleep 20'", detach=True + ) + self.tmp_containers.append(container.id) + exec_output = container.exec_run("sh -c 'exit 42'") + assert exec_output[0] == 42 + def test_exec_run_failed(self): client = docker.from_env(version=TEST_API_VERSION) container = client.containers.run( "alpine", "sh -c 'sleep 60'", detach=True ) self.tmp_containers.append(container.id) - exec_output = container.exec_run("docker ps") - assert exec_output[0] == 126 + exec_output = container.exec_run("non-existent") + # older versions of docker return `126` in the case that an exec cannot + # be started due to a missing executable. We're fixing this for the + # future, so accept both for now. + assert exec_output[0] == 127 or exec_output[0] == 126 def test_kill(self): client = docker.from_env(version=TEST_API_VERSION) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/ssh/api_build_test.py new/docker-py-7.2.0/tests/ssh/api_build_test.py --- old/docker-py-7.1.0/tests/ssh/api_build_test.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/ssh/api_build_test.py 2026-07-09 15:51:20.000000000 +0200 @@ -266,7 +266,7 @@ pass info = self.client.inspect_image('build1') - assert not info['Config']['OnBuild'] + assert 'OnBuild' not in info['Config'] or not info['Config']['OnBuild'] @requires_api_version('1.25') def test_build_with_network_mode(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/docker-py-7.1.0/tests/unit/client_test.py new/docker-py-7.2.0/tests/unit/client_test.py --- old/docker-py-7.1.0/tests/unit/client_test.py 2024-05-23 13:09:32.000000000 +0200 +++ new/docker-py-7.2.0/tests/unit/client_test.py 2026-07-09 15:51:20.000000000 +0200 @@ -9,7 +9,9 @@ from docker.constants import ( DEFAULT_DOCKER_API_VERSION, DEFAULT_MAX_POOL_SIZE, + DEFAULT_NPIPE, DEFAULT_TIMEOUT_SECONDS, + DEFAULT_UNIX_SOCKET, IS_WINDOWS_PLATFORM, ) from docker.utils import kwargs_from_env @@ -188,11 +190,14 @@ assert client.api.timeout == DEFAULT_TIMEOUT_SECONDS @pytest.mark.skipif( - IS_WINDOWS_PLATFORM, reason='Unix Connection Pool only on Linux' + os.environ.get('DOCKER_HOST', '').startswith('tcp://') or IS_WINDOWS_PLATFORM, + reason='Requires a Unix socket' ) @mock.patch("docker.transport.unixconn.UnixHTTPConnectionPool") def test_default_pool_size_from_env_unix(self, mock_obj): - client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + client = docker.from_env( + version=DEFAULT_DOCKER_API_VERSION, use_context=False, + ) mock_obj.return_value.urlopen.return_value.status = 200 client.ping() @@ -219,13 +224,15 @@ ) @pytest.mark.skipif( - IS_WINDOWS_PLATFORM, reason='Unix Connection Pool only on Linux' + os.environ.get('DOCKER_HOST', '').startswith('tcp://') or IS_WINDOWS_PLATFORM, + reason='Requires a Unix socket' ) @mock.patch("docker.transport.unixconn.UnixHTTPConnectionPool") def test_pool_size_from_env_unix(self, mock_obj): client = docker.from_env( version=DEFAULT_DOCKER_API_VERSION, - max_pool_size=POOL_SIZE + max_pool_size=POOL_SIZE, + use_context=False, ) mock_obj.return_value.urlopen.return_value.status = 200 client.ping() @@ -254,3 +261,87 @@ 60, maxsize=POOL_SIZE ) + + +class FromContextTest(unittest.TestCase): + """from_env / from_context honour Docker CLI contexts so the SDK + talks to Docker Desktop (or any other current context) by default.""" + + def setUp(self): + self.os_environ = os.environ.copy() + # Make sure DOCKER_HOST does not short-circuit the context path + os.environ.pop('DOCKER_HOST', None) + os.environ.pop('DOCKER_CONTEXT', None) + + def tearDown(self): + os.environ.clear() + os.environ.update(self.os_environ) + + @mock.patch('docker.client.ContextAPI.kwargs_from_context') + def test_from_env_uses_current_context_when_no_docker_host( + self, mock_ctx): + mock_ctx.return_value = { + 'base_url': 'unix:///Users/me/.docker/run/docker.sock', + } + client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + assert mock_ctx.called + # Unix socket base_urls are rewritten internally; confirm the + # underlying APIClient picked up the context-supplied socket. + assert ( + client.api._custom_adapter.socket_path + == '/Users/me/.docker/run/docker.sock' + ) + + @mock.patch('docker.client.ContextAPI.kwargs_from_context') + def test_from_env_docker_host_overrides_context(self, mock_ctx): + mock_ctx.return_value = { + 'base_url': 'unix:///Users/me/.docker/run/docker.sock', + } + os.environ['DOCKER_HOST'] = 'tcp://192.168.59.103:2375' + client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + assert client.api.base_url == 'http://192.168.59.103:2375' + # When DOCKER_HOST is set we don't need the context fallback. + assert not mock_ctx.called + + @mock.patch('docker.client.ContextAPI.kwargs_from_context') + def test_from_env_use_context_false_skips_context(self, mock_ctx): + client = docker.from_env( + version=DEFAULT_DOCKER_API_VERSION, use_context=False, + ) + assert not mock_ctx.called + # Falls back to APIClient's own platform default. + assert client.api.base_url in ( + 'http+docker://localhost', 'http+docker://localnpipe', + ) + + @mock.patch('docker.client.ContextAPI.kwargs_from_context') + def test_from_context_uses_named_context(self, mock_ctx): + mock_ctx.return_value = { + 'base_url': 'unix:///Users/me/.docker/run/docker.sock', + } + client = docker.from_context( + 'desktop-linux', version=DEFAULT_DOCKER_API_VERSION, + ) + mock_ctx.assert_called_once_with(name='desktop-linux') + assert ( + client.api._custom_adapter.socket_path + == '/Users/me/.docker/run/docker.sock' + ) + + def test_kwargs_from_context_honours_docker_context_env(self): + from docker.context import ContextAPI + # No context with this name exists; helper should return {} rather + # than raise, leaving APIClient to use its own default. + params = ContextAPI.kwargs_from_context( + environment={'DOCKER_CONTEXT': 'does-not-exist'}, + ) + assert params == {} + + def test_kwargs_from_context_default(self): + from docker.context import ContextAPI + params = ContextAPI.kwargs_from_context(name='default') + # The default context always resolves to the local socket / pipe. + assert 'base_url' in params + assert params['base_url'] in ( + DEFAULT_UNIX_SOCKET[len('http+'):], DEFAULT_NPIPE, + )
