diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index ca008b8..c09777c 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -11,8 +11,10 @@ DOCKER_HOST = os.environ.get('DOCKER_HOST', None) -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def docker(request): + os.system("for c in `docker ps -a -q`;do docker rm $c;done") + os.system("for i in `docker images -q`;do docker rmi $i;done") return DockerClient(host=DOCKER_HOST) diff --git a/tests/integration/container_create_test.py b/tests/integration/container_create_test.py new file mode 100644 index 0000000..4567f46 --- /dev/null +++ b/tests/integration/container_create_test.py @@ -0,0 +1,49 @@ +import pytest +import contextlib +import os +import re + +from xd.docker.client import * +from xd.docker.container import * +from xd.docker.parameters import * + + +def test_pull_needed(docker, stdout): + with stdout.redirect(): + container = docker.container_create( + ContainerConfig("busybox:latest"), + "xd-docker-container-create-1") + assert container is not None + assert isinstance(container, Container) + assert re.match('^[0-9a-f]+$', container.id) + + +def test_pull_but_not_needed(docker, stdout): + docker.image_pull("busybox:latest") + with stdout.redirect(): + container = docker.container_create( + ContainerConfig("busybox:latest"), + "xd-docker-container-create-2") + assert container is not None + assert isinstance(container, Container) + assert re.match('^[0-9a-f]+$', container.id) + + +def test_no_pull_but_needed(docker, stdout): + with pytest.raises(ClientError): + docker.container_create( + ContainerConfig("busybox:latest"), + "xd-docker-container-create-3", + pull=False) + + +def test_no_pull_and_not_needed(docker, stdout): + docker.image_pull("busybox:latest") + with stdout.redirect(): + container = docker.container_create( + ContainerConfig("busybox:latest"), + "xd-docker-container-create-4", + pull=False) + assert container is not None + assert isinstance(container, Container) + assert re.match('^[0-9a-f]+$', container.id) diff --git a/tests/integration/image_build_test.py b/tests/integration/image_build_test.py index ad8999c..95b48e7 100644 --- a/tests/integration/image_build_test.py +++ b/tests/integration/image_build_test.py @@ -88,7 +88,7 @@ def test_image_build_9_error(docker, busybox_error, stdout): image = docker.image_build('.', cache=False) assert image is None assert re.search( - '^The command ./bin/sh -c false. returned a non-zero code: 1$', + 'The command ./bin/sh -c false. returned a non-zero code: 1$', stdout.get(), re.M) @@ -97,6 +97,5 @@ def test_image_build_10_error_quiet(docker, busybox_error, stdout): image = docker.image_build('.', cache=False, output=('error')) assert image is None assert re.search( - '^The command ./bin/sh -c false. returned a non-zero code: 1$', + 'The command ./bin/sh -c false. returned a non-zero code: 1$', stdout.get(), re.M) - assert len(stdout.getlines()) == 1 diff --git a/tests/unit/client_test.py b/tests/unit/client_test.py index 0bd6e3f..19a8acf 100644 --- a/tests/unit/client_test.py +++ b/tests/unit/client_test.py @@ -1046,3 +1046,57 @@ def test_container_create_with_exposed_ports(self, post_mock): exposed_ports_arg = data_arg['ExposedPorts'] print(exposed_ports_arg) self.assertEqual(exposed_ports_arg, {'22/tcp': {}, '80/tcp': {}}) + + @mock.patch('requests.get') + @mock.patch('requests.post') + def test_container_create_pull_needed(self, post_mock, get_mock): + post_mock.return_value = self.simple_success_response + get_mock.side_effect = [ + requests_mock.version_response("1.22", "1.10.3"), + requests_mock.Response('404 no such image\n', 404)] + self.client.container_create( + ContainerConfig('busybox:latest'), pull=True) + assert post_mock.call_count == 2 + name, args, kwargs = post_mock.mock_calls[1] + assert args[0].endswith('/containers/create') + + @mock.patch('requests.get') + @mock.patch('requests.post') + def test_container_create_pull_not_needed(self, post_mock, get_mock): + post_mock.return_value = self.simple_success_response + get_mock.side_effect = [ + requests_mock.version_response("1.22", "1.10.3"), + requests_mock.Response(json.dumps( + image_inspect_tests.response), 200)] + self.client.container_create( + ContainerConfig('busybox:latest'), pull=True) + assert post_mock.call_count == 1 + name, args, kwargs = post_mock.mock_calls[0] + assert args[0].endswith('/containers/create') + + @mock.patch('requests.get') + @mock.patch('requests.post') + def test_container_create_nopull_needed(self, post_mock, get_mock): + post_mock.return_value = self.simple_success_response + get_mock.side_effect = [ + requests_mock.version_response("1.22", "1.10.3"), + requests_mock.Response('404 no such image\n', 404)] + self.client.container_create( + ContainerConfig('busybox:latest'), pull=False) + assert post_mock.call_count == 1 + name, args, kwargs = post_mock.mock_calls[0] + assert args[0].endswith('/containers/create') + + @mock.patch('requests.get') + @mock.patch('requests.post') + def test_container_create_nopull_not_needed(self, post_mock, get_mock): + post_mock.return_value = self.simple_success_response + get_mock.side_effect = [ + requests_mock.version_response("1.22", "1.10.3"), + requests_mock.Response(json.dumps( + image_inspect_tests.response), 200)] + self.client.container_create( + ContainerConfig('busybox:latest'), pull=False) + assert post_mock.call_count == 1 + name, args, kwargs = post_mock.mock_calls[0] + assert args[0].endswith('/containers/create') diff --git a/xd/docker/client.py b/xd/docker/client.py index 0925dcb..c5ad43d 100644 --- a/xd/docker/client.py +++ b/xd/docker/client.py @@ -378,7 +378,8 @@ def container_create( config: ContainerConfig, name: Optional[Union[ContainerName, str]]=None, mounts: Optional[Sequence[VolumeMount]]=None, - host_config: Optional[HostConfig]=None): + host_config: Optional[HostConfig]=None, + pull: bool=True): """Create a new container. Create a new container based on existing image. @@ -388,6 +389,7 @@ def container_create( name: name to assign to container. mounts: mount points in the container (list of strings). host_config: HostConfig instance. + pull: Pull image if needed. """ # Handle convenience argument types @@ -415,6 +417,13 @@ def container_create( json_params['ExposedPorts'] = { port: {} for port in json_params['ExposedPorts']} + # Pull image if necessary + if pull: + try: + self.image_inspect_raw(config.image) + except ClientError: + self.image_pull(config.image, output=()) + response = self._post('/containers/create', params=query_params, headers=headers, data=json.dumps(json_params)) response_json = response.json()