diff --git a/docker/models/containers.py b/docker/models/containers.py index 0c2b855a2b..7f40584d0a 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -9,7 +9,7 @@ NotFound, create_unexpected_kwargs_error ) from ..types import HostConfig -from ..utils import version_gte +from ..utils import parse_repository_tag, version_gte from .images import Image from .resource import Collection, Model @@ -541,7 +541,8 @@ def run(self, image, command=None, stdout=True, stderr=False, 'Reticulating spline 1...\\nReticulating spline 2...\\n' Args: - image (str): The image to run. + image (str): The image to run. If the tag is not specified, it will + be set to "latest". command (str or list): The command to run in the container. auto_remove (bool): enable auto-removal of the container on daemon side when the container's process exits. @@ -788,6 +789,11 @@ def run(self, image, command=None, stdout=True, stderr=False, """ if isinstance(image, Image): image = image.id + else: + _, tag = parse_repository_tag(image) + if tag is None: + image += ":latest" + stream = kwargs.pop('stream', False) detach = kwargs.pop('detach', False) platform = kwargs.pop('platform', None) diff --git a/tests/integration/models_containers_test.py b/tests/integration/models_containers_test.py index eac4c97909..f700ddbbd5 100644 --- a/tests/integration/models_containers_test.py +++ b/tests/integration/models_containers_test.py @@ -23,7 +23,7 @@ def test_run_detach(self): client = docker.from_env(version=TEST_API_VERSION) container = client.containers.run("alpine", "sleep 300", detach=True) self.tmp_containers.append(container.id) - assert container.attrs['Config']['Image'] == "alpine" + assert container.attrs['Config']['Image'] == "alpine:latest" assert container.attrs['Config']['Cmd'] == ['sleep', '300'] def test_run_with_error(self): @@ -187,7 +187,7 @@ def test_get(self): container = client.containers.run("alpine", "sleep 300", detach=True) self.tmp_containers.append(container.id) assert client.containers.get(container.id).attrs[ - 'Config']['Image'] == "alpine" + 'Config']['Image'] == "alpine:latest" def test_list(self): client = docker.from_env(version=TEST_API_VERSION) @@ -199,7 +199,7 @@ def test_list(self): assert len(containers) == 1 container = containers[0] - assert container.attrs['Config']['Image'] == 'alpine' + assert container.attrs['Config']['Image'] == 'alpine:latest' assert container.status == 'running' assert container.image == client.images.get('alpine') @@ -217,7 +217,7 @@ def test_list_sparse(self): assert len(containers) == 1 container = containers[0] - assert container.attrs['Image'] == 'alpine' + assert container.attrs['Image'] == 'alpine:latest' assert container.status == 'running' assert container.image == client.images.get('alpine') with pytest.raises(docker.errors.DockerException): diff --git a/tests/unit/models_containers_test.py b/tests/unit/models_containers_test.py index c9f73f3737..9e620780dd 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -17,7 +17,7 @@ def test_run(self): assert out == b'hello world\n' client.api.create_container.assert_called_with( - image="alpine", + image="alpine:latest", command="echo hello world", detach=False, host_config={'NetworkMode': 'default'} @@ -210,7 +210,7 @@ def test_run_detach(self): assert isinstance(container, Container) assert container.id == FAKE_CONTAINER_ID client.api.create_container.assert_called_with( - image='alpine', + image='alpine:latest', command='sleep 300', detach=True, host_config={ @@ -233,7 +233,27 @@ def test_run_pull(self): assert container.id == FAKE_CONTAINER_ID client.api.pull.assert_called_with( - 'alpine', platform=None, tag=None, stream=True + 'alpine', platform=None, tag="latest", stream=True + ) + + def test_run_pull_tag(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 + ] + + container = client.containers.run( + 'alpine:1.0', + 'sleep 300', + detach=True + ) + + assert container.id == FAKE_CONTAINER_ID + client.api.pull.assert_called_with( + 'alpine', platform=None, tag="1.0", stream=True ) def test_run_with_error(self): @@ -296,7 +316,7 @@ def test_run_remove(self): client.api.remove_container.assert_not_called() client.api.create_container.assert_called_with( command=None, - image='alpine', + image='alpine:latest', detach=True, host_config={'AutoRemove': True, 'NetworkMode': 'default'} @@ -308,7 +328,7 @@ def test_run_remove(self): client.api.remove_container.assert_not_called() client.api.create_container.assert_called_with( command=None, - image='alpine', + image='alpine:latest', detach=True, host_config={'AutoRemove': True, 'NetworkMode': 'default'}