mirror of https://github.com/docker/docker-py.git
api: add platform to container create (#2927)
Add platform parameter for container creation/run Signed-off-by: Felix Fontein <felix@fontein.de> Signed-off-by: Milas Bowman <milas.bowman@docker.com> Co-authored-by: Milas Bowman <milas.bowman@docker.com>
This commit is contained in:
parent
26064dd6b5
commit
1a4cacdfb6
|
|
@ -223,7 +223,7 @@ class ContainerApiMixin:
|
||||||
mac_address=None, labels=None, stop_signal=None,
|
mac_address=None, labels=None, stop_signal=None,
|
||||||
networking_config=None, healthcheck=None,
|
networking_config=None, healthcheck=None,
|
||||||
stop_timeout=None, runtime=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
|
Creates a container. Parameters are similar to those for the ``docker
|
||||||
run`` command except it doesn't support the attach options (``-a``).
|
run`` command except it doesn't support the attach options (``-a``).
|
||||||
|
|
@ -398,6 +398,7 @@ class ContainerApiMixin:
|
||||||
configuration file (``~/.docker/config.json`` by default)
|
configuration file (``~/.docker/config.json`` by default)
|
||||||
contains a proxy configuration, the corresponding environment
|
contains a proxy configuration, the corresponding environment
|
||||||
variables will be set in the container being created.
|
variables will be set in the container being created.
|
||||||
|
platform (str): Platform in the format ``os[/arch[/variant]]``.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A dictionary with an image 'Id' key and a 'Warnings' key.
|
A dictionary with an image 'Id' key and a 'Warnings' key.
|
||||||
|
|
@ -427,16 +428,22 @@ class ContainerApiMixin:
|
||||||
stop_signal, networking_config, healthcheck,
|
stop_signal, networking_config, healthcheck,
|
||||||
stop_timeout, runtime
|
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):
|
def create_container_config(self, *args, **kwargs):
|
||||||
return ContainerConfig(self._version, *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")
|
u = self._url("/containers/create")
|
||||||
params = {
|
params = {
|
||||||
'name': name
|
'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)
|
res = self._post_json(u, data=config, params=params)
|
||||||
return self._result(res, True)
|
return self._result(res, True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
import requests
|
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):
|
class DockerException(Exception):
|
||||||
"""
|
"""
|
||||||
|
|
@ -21,10 +30,9 @@ def create_api_error_from_http_exception(e):
|
||||||
explanation = (response.content or '').strip()
|
explanation = (response.content or '').strip()
|
||||||
cls = APIError
|
cls = APIError
|
||||||
if response.status_code == 404:
|
if response.status_code == 404:
|
||||||
if explanation and ('No such image' in str(explanation) or
|
explanation_msg = (explanation or '').lower()
|
||||||
'not found: does not exist or no pull access'
|
if any(fragment in explanation_msg
|
||||||
in str(explanation) or
|
for fragment in _image_not_found_explanation_fragments):
|
||||||
'repository does not exist' in str(explanation)):
|
|
||||||
cls = ImageNotFound
|
cls = ImageNotFound
|
||||||
else:
|
else:
|
||||||
cls = NotFound
|
cls = NotFound
|
||||||
|
|
|
||||||
|
|
@ -801,7 +801,7 @@ class ContainerCollection(Collection):
|
||||||
image = image.id
|
image = image.id
|
||||||
stream = kwargs.pop('stream', False)
|
stream = kwargs.pop('stream', False)
|
||||||
detach = kwargs.pop('detach', False)
|
detach = kwargs.pop('detach', False)
|
||||||
platform = kwargs.pop('platform', None)
|
platform = kwargs.get('platform', None)
|
||||||
|
|
||||||
if detach and remove:
|
if detach and remove:
|
||||||
if version_gte(self.client.api._version, '1.25'):
|
if version_gte(self.client.api._version, '1.25'):
|
||||||
|
|
@ -985,6 +985,7 @@ RUN_CREATE_KWARGS = [
|
||||||
'mac_address',
|
'mac_address',
|
||||||
'name',
|
'name',
|
||||||
'network_disabled',
|
'network_disabled',
|
||||||
|
'platform',
|
||||||
'stdin_open',
|
'stdin_open',
|
||||||
'stop_signal',
|
'stop_signal',
|
||||||
'tty',
|
'tty',
|
||||||
|
|
|
||||||
|
|
@ -348,6 +348,22 @@ class CreateContainerTest(BaseAPIClientTest):
|
||||||
assert args[1]['headers'] == {'Content-Type': 'application/json'}
|
assert args[1]['headers'] == {'Content-Type': 'application/json'}
|
||||||
assert args[1]['params'] == {'name': 'marisa-kirisame'}
|
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):
|
def test_create_container_with_mem_limit_as_int(self):
|
||||||
self.client.create_container(
|
self.client.create_container(
|
||||||
'busybox', 'true', host_config=self.client.create_host_config(
|
'busybox', 'true', host_config=self.client.create_host_config(
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ class ContainerCollectionTest(unittest.TestCase):
|
||||||
oom_score_adj=5,
|
oom_score_adj=5,
|
||||||
pid_mode='host',
|
pid_mode='host',
|
||||||
pids_limit=500,
|
pids_limit=500,
|
||||||
|
platform='linux',
|
||||||
ports={
|
ports={
|
||||||
1111: 4567,
|
1111: 4567,
|
||||||
2222: None
|
2222: None
|
||||||
|
|
@ -186,6 +187,7 @@ class ContainerCollectionTest(unittest.TestCase):
|
||||||
name='somename',
|
name='somename',
|
||||||
network_disabled=False,
|
network_disabled=False,
|
||||||
networking_config={'foo': None},
|
networking_config={'foo': None},
|
||||||
|
platform='linux',
|
||||||
ports=[('1111', 'tcp'), ('2222', 'tcp')],
|
ports=[('1111', 'tcp'), ('2222', 'tcp')],
|
||||||
stdin_open=True,
|
stdin_open=True,
|
||||||
stop_signal=9,
|
stop_signal=9,
|
||||||
|
|
@ -314,6 +316,33 @@ class ContainerCollectionTest(unittest.TestCase):
|
||||||
'NetworkMode': 'default'}
|
'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):
|
def test_create(self):
|
||||||
client = make_fake_client()
|
client = make_fake_client()
|
||||||
container = client.containers.create(
|
container = client.containers.create(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue