-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add support for platform parameter in image push #3325
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ba505bf
1cc084a
3f89d1e
3f6152b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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__) | ||
|
|
||
|
|
@@ -434,7 +435,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 +449,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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be an instance of the new |
||
|
|
||
| Returns: | ||
| (generator or str): The output from the server. | ||
|
|
@@ -488,6 +490,16 @@ 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' | ||
| ) | ||
| # Handle both Platform instances and dict inputs | ||
| if isinstance(platform, dict): | ||
| platform = Platform(**platform) | ||
| params['platform'] = platform | ||
|
|
||
| response = self._post_json( | ||
| u, None, headers=headers, stream=stream, params=params | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| from .base import DictType | ||
|
|
||
|
|
||
| class Platform(DictType): | ||
| def __init__(self, **kwargs): | ||
| architecture = kwargs.get('architecture') | ||
| os = kwargs.get('os') | ||
|
|
||
| if architecture is None or os is None: | ||
| raise ValueError("Both 'architecture' and 'os' must be provided") | ||
|
|
||
| super().__init__({ | ||
| 'architecture': architecture, | ||
| 'os': os, | ||
| 'os_version': kwargs.get('os_version'), | ||
| 'os_features': kwargs.get('os_features'), | ||
| '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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,55 @@ def test_push_image_with_auth(self): | |
| timeout=DEFAULT_TIMEOUT_SECONDS | ||
| ) | ||
|
|
||
| @requires_api_version('1.46') | ||
| def test_push_image_with_platform(self): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not to be picky, but your function can take either Platform OR dict, but you are just testing one.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No issue, good point. I'll see towards that. |
||
| 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 | ||
| ) | ||
|
|
||
| @requires_api_version('1.46') | ||
| def test_push_image_with_platform_dict(self): | ||
| platform_dict = {'os': 'linux', 'architecture': 'arm', 'variant': 'v7'} | ||
| with mock.patch('docker.auth.resolve_authconfig', | ||
| fake_resolve_authconfig): | ||
| self.client.push( | ||
| fake_api.FAKE_IMAGE_NAME, | ||
| platform=platform_dict | ||
| ) | ||
|
|
||
| # When passed as dict, it should be converted to Platform instance | ||
| from docker.types.image import Platform | ||
| expected_platform = Platform(**platform_dict) | ||
|
|
||
| fake_request.assert_called_with( | ||
| 'POST', | ||
| f"{url_prefix}images/test_image/push", | ||
| params={ | ||
| 'tag': None, | ||
| 'platform': expected_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): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's expected to be a JSON-encoded at the API level. But at the SDK level, it would probably be better if it was an instance of a nicer "Platform" class/struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whatever the case shouldn’t it match the pull? There shouldn’t be a string in one and class in another.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, these have different format. Pull accepts a plain platform string like
linux/amd64, but this one is a full JSON representing an OCI platform.But you're right, that it's inconsistent and we probably should fix it at some point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vvoland can you take a look at this?