From 2356a177c0d137c30d63cc6271a8a9e5c0a92414 Mon Sep 17 00:00:00 2001 From: Khushiyant Date: Fri, 28 Mar 2025 19:19:08 +0530 Subject: [PATCH 1/2] feat: add platform parameter to image push method and corresponding test Signed-off-by: Khushiyant --- docker/api/image.py | 10 +++++++++- tests/unit/api_image_test.py | 23 +++++++++++++++++++++++ tests/unit/fake_api.py | 1 + 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/docker/api/image.py b/docker/api/image.py index 85109473b..3c101fd57 100644 --- a/docker/api/image.py +++ b/docker/api/image.py @@ -434,7 +434,7 @@ def pull(self, repository, tag=None, stream=False, auth_config=None, return self._result(response) def push(self, repository, tag=None, stream=False, auth_config=None, - decode=False): + decode=False, platform=None): """ Push an image or a repository to the registry. Similar to the ``docker push`` command. @@ -448,6 +448,7 @@ def push(self, repository, tag=None, stream=False, auth_config=None, ``username`` and ``password`` keys to be valid. decode (bool): Decode the JSON data from the server into dicts. Only applies with ``stream=True`` + platform (str): JSON-encoded OCI platform to select the platform-variant to push. If not provided, all available variants will attempt to be pushed. Returns: (generator or str): The output from the server. @@ -488,6 +489,13 @@ def push(self, repository, tag=None, stream=False, auth_config=None, log.debug('Sending supplied auth config') headers['X-Registry-Auth'] = auth.encode_header(auth_config) + if platform is not None: + if utils.version_lt(self._version, '1.46'): + raise errors.InvalidVersion( + 'platform was only introduced in API version 1.46' + ) + params['platform'] = platform + response = self._post_json( u, None, headers=headers, stream=stream, params=params ) diff --git a/tests/unit/api_image_test.py b/tests/unit/api_image_test.py index 148109d37..2e567293f 100644 --- a/tests/unit/api_image_test.py +++ b/tests/unit/api_image_test.py @@ -5,6 +5,7 @@ import docker from docker import auth +from ..helpers import requires_api_version from . import fake_api from .api_test import ( DEFAULT_TIMEOUT_SECONDS, @@ -271,6 +272,28 @@ def test_push_image_with_auth(self): timeout=DEFAULT_TIMEOUT_SECONDS ) + @requires_api_version('1.46') + def test_push_image_with_platform(self): + with mock.patch('docker.auth.resolve_authconfig', + fake_resolve_authconfig): + self.client.push( + fake_api.FAKE_IMAGE_NAME, + platform=fake_api.FAKE_PLATFORM + ) + + fake_request.assert_called_with( + 'POST', + f"{url_prefix}images/test_image/push", + params={ + 'tag': None, + 'platform': fake_api.FAKE_PLATFORM + }, + data='{}', + headers={'Content-Type': 'application/json'}, + stream=False, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + def test_push_image_stream(self): with mock.patch('docker.auth.resolve_authconfig', fake_resolve_authconfig): diff --git a/tests/unit/fake_api.py b/tests/unit/fake_api.py index 03e53cc64..fd9936709 100644 --- a/tests/unit/fake_api.py +++ b/tests/unit/fake_api.py @@ -21,6 +21,7 @@ FAKE_SECRET_NAME = 'super_secret' FAKE_CONFIG_ID = 'sekvs771242jfdjnvfuds8232' FAKE_CONFIG_NAME = 'super_config' +FAKE_PLATFORM = "{'os': 'linux','architecture': 'arm','variant': 'v5'}" # Each method is prefixed with HTTP method (get, post...) # for clarity and readability From 958049e01d19a7922fe42f6f8516b2ddf0ae498f Mon Sep 17 00:00:00 2001 From: Khushiyant Date: Mon, 30 Jun 2025 13:26:34 +0200 Subject: [PATCH 2/2] feat: introduce Platform class instead of JSON --- docker/api/image.py | 3 ++- docker/types/image.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 docker/types/image.py diff --git a/docker/api/image.py b/docker/api/image.py index 3c101fd57..eab0a771c 100644 --- a/docker/api/image.py +++ b/docker/api/image.py @@ -3,6 +3,7 @@ from .. import auth, errors, utils from ..constants import DEFAULT_DATA_CHUNK_SIZE +from ..types.image import Platform log = logging.getLogger(__name__) @@ -494,7 +495,7 @@ def push(self, repository, tag=None, stream=False, auth_config=None, raise errors.InvalidVersion( 'platform was only introduced in API version 1.46' ) - params['platform'] = platform + params['platform'] = Platform response = self._post_json( u, None, headers=headers, stream=stream, params=params diff --git a/docker/types/image.py b/docker/types/image.py new file mode 100644 index 000000000..2533c0ec8 --- /dev/null +++ b/docker/types/image.py @@ -0,0 +1,35 @@ +from .base import DictType + + +class Platform(DictType): + def __init__(self, **kwargs): + architecture = kwargs.get('architecture', kwargs.get('Architecture')) + os = kwargs.get('os', kwargs.get('OS')) + + if architecture is None and os is None: + raise ValueError("At least one of 'architecture' or 'os' must be provided") + + + super().__init__({ + 'Architecture': architecture, + 'OS': os, + 'OSVersion': kwargs.get('os_version', kwargs.get('OSVersion')), + 'OSFeatures': kwargs.get('os_features', kwargs.get('OSFeatures')), + 'Variant': kwargs.get('variant', kwargs.get('Variant')) + }) + + @property + def architecture(self): + return self['Architecture'] + + @property + def os(self): + return self['OS'] + + @architecture.setter + def architecture(self, value): + self['Architecture'] = value + + @os.setter + def os(self, value): + self['OS'] = value