Skip to content

Commit

Permalink
api: add platform to container create (#2927)
Browse files Browse the repository at this point in the history
Add platform parameter for container creation/run

Signed-off-by: Felix Fontein <[email protected]>
Signed-off-by: Milas Bowman <[email protected]>
Co-authored-by: Milas Bowman <[email protected]>
  • Loading branch information
felixfontein and milas authored Jul 29, 2022
1 parent 26064dd commit 1a4cacd
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 8 deletions.
13 changes: 10 additions & 3 deletions docker/api/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def create_container(self, image, command=None, hostname=None, user=None,
mac_address=None, labels=None, stop_signal=None,
networking_config=None, healthcheck=None,
stop_timeout=None, runtime=None,
use_config_proxy=True):
use_config_proxy=True, platform=None):
"""
Creates a container. Parameters are similar to those for the ``docker
run`` command except it doesn't support the attach options (``-a``).
Expand Down Expand Up @@ -398,6 +398,7 @@ def create_container(self, image, command=None, hostname=None, user=None,
configuration file (``~/.docker/config.json`` by default)
contains a proxy configuration, the corresponding environment
variables will be set in the container being created.
platform (str): Platform in the format ``os[/arch[/variant]]``.
Returns:
A dictionary with an image 'Id' key and a 'Warnings' key.
Expand Down Expand Up @@ -427,16 +428,22 @@ def create_container(self, image, command=None, hostname=None, user=None,
stop_signal, networking_config, healthcheck,
stop_timeout, runtime
)
return self.create_container_from_config(config, name)
return self.create_container_from_config(config, name, platform)

def create_container_config(self, *args, **kwargs):
return ContainerConfig(self._version, *args, **kwargs)

def create_container_from_config(self, config, name=None):
def create_container_from_config(self, config, name=None, platform=None):
u = self._url("/containers/create")
params = {
'name': name
}
if platform:
if utils.version_lt(self._version, '1.41'):
raise errors.InvalidVersion(
'platform is not supported for API version < 1.41'
)
params['platform'] = platform
res = self._post_json(u, data=config, params=params)
return self._result(res, True)

Expand Down
16 changes: 12 additions & 4 deletions docker/errors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import requests

_image_not_found_explanation_fragments = frozenset(
fragment.lower() for fragment in [
'no such image',
'not found: does not exist or no pull access',
'repository does not exist',
'was found but does not match the specified platform',
]
)


class DockerException(Exception):
"""
Expand All @@ -21,10 +30,9 @@ def create_api_error_from_http_exception(e):
explanation = (response.content or '').strip()
cls = APIError
if response.status_code == 404:
if explanation and ('No such image' in str(explanation) or
'not found: does not exist or no pull access'
in str(explanation) or
'repository does not exist' in str(explanation)):
explanation_msg = (explanation or '').lower()
if any(fragment in explanation_msg
for fragment in _image_not_found_explanation_fragments):
cls = ImageNotFound
else:
cls = NotFound
Expand Down
3 changes: 2 additions & 1 deletion docker/models/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,7 @@ def run(self, image, command=None, stdout=True, stderr=False,
image = image.id
stream = kwargs.pop('stream', False)
detach = kwargs.pop('detach', False)
platform = kwargs.pop('platform', None)
platform = kwargs.get('platform', None)

if detach and remove:
if version_gte(self.client.api._version, '1.25'):
Expand Down Expand Up @@ -985,6 +985,7 @@ def prune(self, filters=None):
'mac_address',
'name',
'network_disabled',
'platform',
'stdin_open',
'stop_signal',
'tty',
Expand Down
16 changes: 16 additions & 0 deletions tests/unit/api_container_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,22 @@ def test_create_named_container(self):
assert args[1]['headers'] == {'Content-Type': 'application/json'}
assert args[1]['params'] == {'name': 'marisa-kirisame'}

def test_create_container_with_platform(self):
self.client.create_container('busybox', 'true',
platform='linux')

args = fake_request.call_args
assert args[0][1] == url_prefix + 'containers/create'
assert json.loads(args[1]['data']) == json.loads('''
{"Tty": false, "Image": "busybox", "Cmd": ["true"],
"AttachStdin": false,
"AttachStderr": true, "AttachStdout": true,
"StdinOnce": false,
"OpenStdin": false, "NetworkDisabled": false}
''')
assert args[1]['headers'] == {'Content-Type': 'application/json'}
assert args[1]['params'] == {'name': None, 'platform': 'linux'}

def test_create_container_with_mem_limit_as_int(self):
self.client.create_container(
'busybox', 'true', host_config=self.client.create_host_config(
Expand Down
29 changes: 29 additions & 0 deletions tests/unit/models_containers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def test_create_container_args(self):
oom_score_adj=5,
pid_mode='host',
pids_limit=500,
platform='linux',
ports={
1111: 4567,
2222: None
Expand Down Expand Up @@ -186,6 +187,7 @@ def test_create_container_args(self):
name='somename',
network_disabled=False,
networking_config={'foo': None},
platform='linux',
ports=[('1111', 'tcp'), ('2222', 'tcp')],
stdin_open=True,
stop_signal=9,
Expand Down Expand Up @@ -314,6 +316,33 @@ def test_run_remove(self):
'NetworkMode': 'default'}
)

def test_run_platform(self):
client = make_fake_client()

# raise exception on first call, then return normal value
client.api.create_container.side_effect = [
docker.errors.ImageNotFound(""),
client.api.create_container.return_value
]

client.containers.run(image='alpine', platform='linux/arm64')

client.api.pull.assert_called_with(
'alpine',
tag='latest',
all_tags=False,
stream=True,
platform='linux/arm64',
)

client.api.create_container.assert_called_with(
detach=False,
platform='linux/arm64',
image='alpine',
command=None,
host_config={'NetworkMode': 'default'},
)

def test_create(self):
client = make_fake_client()
container = client.containers.create(
Expand Down

0 comments on commit 1a4cacd

Please sign in to comment.