diff options
author | Felipe Sateler <fsateler@debian.org> | 2019-11-22 20:51:27 -0300 |
---|---|---|
committer | Felipe Sateler <fsateler@debian.org> | 2019-11-22 20:51:27 -0300 |
commit | 3f9a8392bb8dd13baef20d58914f822202947664 (patch) | |
tree | 2b710defcc6f1351e83f4ddba795032a6444a6c8 /tests/integration | |
parent | 0e08ec700f3e27bee8da6605cdd93491a14454dd (diff) | |
parent | 001f034ab762eab7724e4c2bc955b7f3f3dc8504 (diff) |
Merge tag 'upstream/4.1.0'
Upstream version 4.1.0
Diffstat (limited to 'tests/integration')
-rw-r--r-- | tests/integration/api_build_test.py | 58 | ||||
-rw-r--r-- | tests/integration/api_client_test.py | 2 | ||||
-rw-r--r-- | tests/integration/api_container_test.py | 272 | ||||
-rw-r--r-- | tests/integration/api_exec_test.py | 181 | ||||
-rw-r--r-- | tests/integration/api_healthcheck_test.py | 10 | ||||
-rw-r--r-- | tests/integration/api_image_test.py | 24 | ||||
-rw-r--r-- | tests/integration/api_network_test.py | 20 | ||||
-rw-r--r-- | tests/integration/api_plugin_test.py | 16 | ||||
-rw-r--r-- | tests/integration/api_service_test.py | 155 | ||||
-rw-r--r-- | tests/integration/api_swarm_test.py | 54 | ||||
-rw-r--r-- | tests/integration/base.py | 80 | ||||
-rw-r--r-- | tests/integration/conftest.py | 10 | ||||
-rw-r--r-- | tests/integration/credentials/__init__.py | 0 | ||||
-rw-r--r-- | tests/integration/credentials/store_test.py | 87 | ||||
-rw-r--r-- | tests/integration/credentials/utils_test.py | 22 | ||||
-rw-r--r-- | tests/integration/errors_test.py | 4 | ||||
-rw-r--r-- | tests/integration/models_containers_test.py | 102 | ||||
-rw-r--r-- | tests/integration/models_images_test.py | 35 | ||||
-rw-r--r-- | tests/integration/models_swarm_test.py | 12 | ||||
-rw-r--r-- | tests/integration/regression_test.py | 12 |
20 files changed, 854 insertions, 302 deletions
diff --git a/tests/integration/api_build_test.py b/tests/integration/api_build_test.py index baaf33e..5712812 100644 --- a/tests/integration/api_build_test.py +++ b/tests/integration/api_build_test.py @@ -4,15 +4,58 @@ import shutil import tempfile from docker import errors +from docker.utils.proxy import ProxyConfig import pytest import six -from .base import BaseAPIIntegrationTest, BUSYBOX +from .base import BaseAPIIntegrationTest, TEST_IMG from ..helpers import random_name, requires_api_version, requires_experimental class BuildTest(BaseAPIIntegrationTest): + def test_build_with_proxy(self): + self.client._proxy_configs = ProxyConfig( + ftp='a', http='b', https='c', no_proxy='d' + ) + + script = io.BytesIO('\n'.join([ + 'FROM busybox', + 'RUN env | grep "FTP_PROXY=a"', + 'RUN env | grep "ftp_proxy=a"', + 'RUN env | grep "HTTP_PROXY=b"', + 'RUN env | grep "http_proxy=b"', + 'RUN env | grep "HTTPS_PROXY=c"', + 'RUN env | grep "https_proxy=c"', + 'RUN env | grep "NO_PROXY=d"', + 'RUN env | grep "no_proxy=d"', + ]).encode('ascii')) + + self.client.build(fileobj=script, decode=True) + + def test_build_with_proxy_and_buildargs(self): + self.client._proxy_configs = ProxyConfig( + ftp='a', http='b', https='c', no_proxy='d' + ) + + script = io.BytesIO('\n'.join([ + 'FROM busybox', + 'RUN env | grep "FTP_PROXY=XXX"', + 'RUN env | grep "ftp_proxy=xxx"', + 'RUN env | grep "HTTP_PROXY=b"', + 'RUN env | grep "http_proxy=b"', + 'RUN env | grep "HTTPS_PROXY=c"', + 'RUN env | grep "https_proxy=c"', + 'RUN env | grep "NO_PROXY=d"', + 'RUN env | grep "no_proxy=d"', + ]).encode('ascii')) + + self.client.build( + fileobj=script, + decode=True, + buildargs={'FTP_PROXY': 'XXX', 'ftp_proxy': 'xxx'} + ) + def test_build_streaming(self): script = io.BytesIO('\n'.join([ 'FROM busybox', @@ -234,7 +277,7 @@ class BuildTest(BaseAPIIntegrationTest): # Set up pingable endpoint on custom network network = self.client.create_network(random_name())['Id'] self.tmp_networks.append(network) - container = self.client.create_container(BUSYBOX, 'top') + container = self.client.create_container(TEST_IMG, 'top') self.tmp_containers.append(container) self.client.start(container) self.client.connect_container_to_network( @@ -405,8 +448,10 @@ class BuildTest(BaseAPIIntegrationTest): for _ in stream: pass - assert excinfo.value.status_code == 400 - assert 'invalid platform' in excinfo.exconly() + # Some API versions incorrectly returns 500 status; assert 4xx or 5xx + assert excinfo.value.is_error() + assert 'unknown operating system' in excinfo.exconly() \ + or 'invalid platform' in excinfo.exconly() def test_build_out_of_context_dockerfile(self): base_dir = tempfile.mkdtemp() @@ -540,6 +585,11 @@ class BuildTest(BaseAPIIntegrationTest): ) == sorted(lsdata) @requires_api_version('1.31') + @pytest.mark.xfail( + True, + reason='Currently fails on 18.09: ' + 'https://github.com/moby/moby/issues/37920' + ) def test_prune_builds(self): prune_result = self.client.prune_builds() assert 'SpaceReclaimed' in prune_result diff --git a/tests/integration/api_client_test.py b/tests/integration/api_client_test.py index 905e064..9e348f3 100644 --- a/tests/integration/api_client_test.py +++ b/tests/integration/api_client_test.py @@ -47,7 +47,7 @@ class ConnectionTimeoutTest(unittest.TestCase): # This call isn't supposed to complete, and it should fail fast. try: res = self.client.inspect_container('id') - except: + except: # noqa: E722 pass end = time.time() assert res is None diff --git a/tests/integration/api_container_test.py b/tests/integration/api_container_test.py index ff70148..1ba3eaa 100644 --- a/tests/integration/api_container_test.py +++ b/tests/integration/api_container_test.py @@ -5,28 +5,27 @@ import tempfile import threading from datetime import datetime -import docker -from docker.constants import IS_WINDOWS_PLATFORM -from docker.utils.socket import next_frame_size -from docker.utils.socket import read_exactly - import pytest - import requests import six -from .base import BUSYBOX, BaseAPIIntegrationTest +import docker from .. import helpers -from ..helpers import ( - requires_api_version, ctrl_with, assert_cat_socket_detached_with_keys -) +from ..helpers import assert_cat_socket_detached_with_keys +from ..helpers import ctrl_with +from ..helpers import requires_api_version +from .base import BaseAPIIntegrationTest +from .base import TEST_IMG +from docker.constants import IS_WINDOWS_PLATFORM +from docker.utils.socket import next_frame_header +from docker.utils.socket import read_exactly class ListContainersTest(BaseAPIIntegrationTest): def test_list_containers(self): res0 = self.client.containers(all=True) size = len(res0) - res1 = self.client.create_container(BUSYBOX, 'true') + res1 = self.client.create_container(TEST_IMG, 'true') assert 'Id' in res1 self.client.start(res1['Id']) self.tmp_containers.append(res1['Id']) @@ -38,20 +37,20 @@ class ListContainersTest(BaseAPIIntegrationTest): assert 'Command' in retrieved assert retrieved['Command'] == six.text_type('true') assert 'Image' in retrieved - assert re.search(r'busybox:.*', retrieved['Image']) + assert re.search(r'alpine:.*', retrieved['Image']) assert 'Status' in retrieved class CreateContainerTest(BaseAPIIntegrationTest): def test_create(self): - res = self.client.create_container(BUSYBOX, 'true') + res = self.client.create_container(TEST_IMG, 'true') assert 'Id' in res self.tmp_containers.append(res['Id']) def test_create_with_host_pid_mode(self): ctnr = self.client.create_container( - BUSYBOX, 'true', host_config=self.client.create_host_config( + TEST_IMG, 'true', host_config=self.client.create_host_config( pid_mode='host', network_mode='none' ) ) @@ -66,7 +65,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): def test_create_with_links(self): res0 = self.client.create_container( - BUSYBOX, 'cat', + TEST_IMG, 'cat', detach=True, stdin_open=True, environment={'FOO': '1'}) @@ -76,7 +75,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): self.client.start(container1_id) res1 = self.client.create_container( - BUSYBOX, 'cat', + TEST_IMG, 'cat', detach=True, stdin_open=True, environment={'FOO': '1'}) @@ -95,7 +94,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): link_env_prefix2 = link_alias2.upper() res2 = self.client.create_container( - BUSYBOX, 'env', host_config=self.client.create_host_config( + TEST_IMG, 'env', host_config=self.client.create_host_config( links={link_path1: link_alias1, link_path2: link_alias2}, network_mode='bridge' ) @@ -115,7 +114,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): def test_create_with_restart_policy(self): container = self.client.create_container( - BUSYBOX, ['sleep', '2'], + TEST_IMG, ['sleep', '2'], host_config=self.client.create_host_config( restart_policy={"Name": "always", "MaximumRetryCount": 0}, network_mode='none' @@ -134,21 +133,21 @@ class CreateContainerTest(BaseAPIIntegrationTest): vol_names = ['foobar_vol0', 'foobar_vol1'] res0 = self.client.create_container( - BUSYBOX, 'true', name=vol_names[0] + TEST_IMG, 'true', name=vol_names[0] ) container1_id = res0['Id'] self.tmp_containers.append(container1_id) self.client.start(container1_id) res1 = self.client.create_container( - BUSYBOX, 'true', name=vol_names[1] + TEST_IMG, 'true', name=vol_names[1] ) container2_id = res1['Id'] self.tmp_containers.append(container2_id) self.client.start(container2_id) res = self.client.create_container( - BUSYBOX, 'cat', detach=True, stdin_open=True, + TEST_IMG, 'cat', detach=True, stdin_open=True, host_config=self.client.create_host_config( volumes_from=vol_names, network_mode='none' ) @@ -162,7 +161,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): def create_container_readonly_fs(self): ctnr = self.client.create_container( - BUSYBOX, ['mkdir', '/shrine'], + TEST_IMG, ['mkdir', '/shrine'], host_config=self.client.create_host_config( read_only=True, network_mode='none' ) @@ -174,7 +173,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): assert res != 0 def create_container_with_name(self): - res = self.client.create_container(BUSYBOX, 'true', name='foobar') + res = self.client.create_container(TEST_IMG, 'true', name='foobar') assert 'Id' in res self.tmp_containers.append(res['Id']) inspect = self.client.inspect_container(res['Id']) @@ -183,7 +182,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): def create_container_privileged(self): res = self.client.create_container( - BUSYBOX, 'true', host_config=self.client.create_host_config( + TEST_IMG, 'true', host_config=self.client.create_host_config( privileged=True, network_mode='none' ) ) @@ -209,7 +208,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): def test_create_with_mac_address(self): mac_address_expected = "02:42:ac:11:00:0a" container = self.client.create_container( - BUSYBOX, ['sleep', '60'], mac_address=mac_address_expected) + TEST_IMG, ['sleep', '60'], mac_address=mac_address_expected) id = container['Id'] @@ -221,7 +220,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): def test_group_id_ints(self): container = self.client.create_container( - BUSYBOX, 'id -G', + TEST_IMG, 'id -G', host_config=self.client.create_host_config(group_add=[1000, 1001]) ) self.tmp_containers.append(container) @@ -237,7 +236,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): def test_group_id_strings(self): container = self.client.create_container( - BUSYBOX, 'id -G', host_config=self.client.create_host_config( + TEST_IMG, 'id -G', host_config=self.client.create_host_config( group_add=['1000', '1001'] ) ) @@ -260,7 +259,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): ) container = self.client.create_container( - BUSYBOX, ['true'], + TEST_IMG, ['true'], host_config=self.client.create_host_config(log_config=log_config) ) self.tmp_containers.append(container['Id']) @@ -282,7 +281,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): with pytest.raises(docker.errors.APIError) as excinfo: # raises an internal server error 500 container = self.client.create_container( - BUSYBOX, ['true'], host_config=self.client.create_host_config( + TEST_IMG, ['true'], host_config=self.client.create_host_config( log_config=log_config ) ) @@ -297,7 +296,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): ) container = self.client.create_container( - BUSYBOX, ['true'], + TEST_IMG, ['true'], host_config=self.client.create_host_config(log_config=log_config) ) self.tmp_containers.append(container['Id']) @@ -316,7 +315,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): ) container = self.client.create_container( - BUSYBOX, ['true'], + TEST_IMG, ['true'], host_config=self.client.create_host_config(log_config=log_config) ) self.tmp_containers.append(container['Id']) @@ -330,7 +329,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): def test_create_with_memory_constraints_with_str(self): ctnr = self.client.create_container( - BUSYBOX, 'true', + TEST_IMG, 'true', host_config=self.client.create_host_config( memswap_limit='1G', mem_limit='700M' @@ -348,7 +347,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): def test_create_with_memory_constraints_with_int(self): ctnr = self.client.create_container( - BUSYBOX, 'true', + TEST_IMG, 'true', host_config=self.client.create_host_config(mem_swappiness=40) ) assert 'Id' in ctnr @@ -362,16 +361,15 @@ class CreateContainerTest(BaseAPIIntegrationTest): def test_create_with_environment_variable_no_value(self): container = self.client.create_container( - BUSYBOX, + TEST_IMG, ['echo'], environment={'Foo': None, 'Other': 'one', 'Blank': ''}, ) self.tmp_containers.append(container['Id']) config = self.client.inspect_container(container['Id']) - assert ( - sorted(config['Config']['Env']) == - sorted(['Foo', 'Other=one', 'Blank=']) - ) + assert 'Foo' in config['Config']['Env'] + assert 'Other=one' in config['Config']['Env'] + assert 'Blank=' in config['Config']['Env'] @requires_api_version('1.22') def test_create_with_tmpfs(self): @@ -380,7 +378,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): } container = self.client.create_container( - BUSYBOX, + TEST_IMG, ['echo'], host_config=self.client.create_host_config( tmpfs=tmpfs)) @@ -392,7 +390,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): @requires_api_version('1.24') def test_create_with_isolation(self): container = self.client.create_container( - BUSYBOX, ['echo'], host_config=self.client.create_host_config( + TEST_IMG, ['echo'], host_config=self.client.create_host_config( isolation='default' ) ) @@ -406,7 +404,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): auto_remove=True ) container = self.client.create_container( - BUSYBOX, ['echo', 'test'], host_config=host_config + TEST_IMG, ['echo', 'test'], host_config=host_config ) self.tmp_containers.append(container['Id']) config = self.client.inspect_container(container) @@ -415,7 +413,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): @requires_api_version('1.25') def test_create_with_stop_timeout(self): container = self.client.create_container( - BUSYBOX, ['echo', 'test'], stop_timeout=25 + TEST_IMG, ['echo', 'test'], stop_timeout=25 ) self.tmp_containers.append(container['Id']) config = self.client.inspect_container(container) @@ -428,7 +426,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): storage_opt={'size': '120G'} ) container = self.client.create_container( - BUSYBOX, ['echo', 'test'], host_config=host_config + TEST_IMG, ['echo', 'test'], host_config=host_config ) self.tmp_containers.append(container) config = self.client.inspect_container(container) @@ -439,7 +437,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): @requires_api_version('1.25') def test_create_with_init(self): ctnr = self.client.create_container( - BUSYBOX, 'true', + TEST_IMG, 'true', host_config=self.client.create_host_config( init=True ) @@ -448,25 +446,12 @@ class CreateContainerTest(BaseAPIIntegrationTest): config = self.client.inspect_container(ctnr) assert config['HostConfig']['Init'] is True - @pytest.mark.xfail(True, reason='init-path removed in 17.05.0') - @requires_api_version('1.25') - def test_create_with_init_path(self): - ctnr = self.client.create_container( - BUSYBOX, 'true', - host_config=self.client.create_host_config( - init_path="/usr/libexec/docker-init" - ) - ) - self.tmp_containers.append(ctnr['Id']) - config = self.client.inspect_container(ctnr) - assert config['HostConfig']['InitPath'] == "/usr/libexec/docker-init" - @requires_api_version('1.24') @pytest.mark.xfail(not os.path.exists('/sys/fs/cgroup/cpu.rt_runtime_us'), reason='CONFIG_RT_GROUP_SCHED isn\'t enabled') def test_create_with_cpu_rt_options(self): ctnr = self.client.create_container( - BUSYBOX, 'true', host_config=self.client.create_host_config( + TEST_IMG, 'true', host_config=self.client.create_host_config( cpu_rt_period=1000, cpu_rt_runtime=500 ) ) @@ -479,7 +464,7 @@ class CreateContainerTest(BaseAPIIntegrationTest): def test_create_with_device_cgroup_rules(self): rule = 'c 7:128 rwm' ctnr = self.client.create_container( - BUSYBOX, 'cat /sys/fs/cgroup/devices/devices.list', + TEST_IMG, 'cat /sys/fs/cgroup/devices/devices.list', host_config=self.client.create_host_config( device_cgroup_rules=[rule] ) @@ -490,6 +475,16 @@ class CreateContainerTest(BaseAPIIntegrationTest): self.client.start(ctnr) assert rule in self.client.logs(ctnr).decode('utf-8') + def test_create_with_uts_mode(self): + container = self.client.create_container( + TEST_IMG, ['echo'], host_config=self.client.create_host_config( + uts_mode='host' + ) + ) + self.tmp_containers.append(container) + config = self.client.inspect_container(container) + assert config['HostConfig']['UTSMode'] == 'host' + @pytest.mark.xfail( IS_WINDOWS_PLATFORM, reason='Test not designed for Windows platform' @@ -506,7 +501,7 @@ class VolumeBindTest(BaseAPIIntegrationTest): self.run_with_volume( False, - BUSYBOX, + TEST_IMG, ['touch', os.path.join(self.mount_dest, self.filename)], ) @@ -514,7 +509,7 @@ class VolumeBindTest(BaseAPIIntegrationTest): container = self.run_with_volume( False, - BUSYBOX, + TEST_IMG, ['ls', self.mount_dest], ) logs = self.client.logs(container) @@ -528,12 +523,12 @@ class VolumeBindTest(BaseAPIIntegrationTest): def test_create_with_binds_ro(self): self.run_with_volume( False, - BUSYBOX, + TEST_IMG, ['touch', os.path.join(self.mount_dest, self.filename)], ) container = self.run_with_volume( True, - BUSYBOX, + TEST_IMG, ['ls', self.mount_dest], ) logs = self.client.logs(container) @@ -552,7 +547,7 @@ class VolumeBindTest(BaseAPIIntegrationTest): ) host_config = self.client.create_host_config(mounts=[mount]) container = self.run_container( - BUSYBOX, ['ls', self.mount_dest], + TEST_IMG, ['ls', self.mount_dest], host_config=host_config ) assert container @@ -571,7 +566,7 @@ class VolumeBindTest(BaseAPIIntegrationTest): ) host_config = self.client.create_host_config(mounts=[mount]) container = self.run_container( - BUSYBOX, ['ls', self.mount_dest], + TEST_IMG, ['ls', self.mount_dest], host_config=host_config ) assert container @@ -590,7 +585,7 @@ class VolumeBindTest(BaseAPIIntegrationTest): ) host_config = self.client.create_host_config(mounts=[mount]) container = self.client.create_container( - BUSYBOX, ['true'], host_config=host_config, + TEST_IMG, ['true'], host_config=host_config, ) assert container inspect_data = self.client.inspect_container(container) @@ -636,7 +631,7 @@ class ArchiveTest(BaseAPIIntegrationTest): def test_get_file_archive_from_container(self): data = 'The Maid and the Pocket Watch of Blood' ctnr = self.client.create_container( - BUSYBOX, 'sh -c "echo {0} > /vol1/data.txt"'.format(data), + TEST_IMG, 'sh -c "echo {0} > /vol1/data.txt"'.format(data), volumes=['/vol1'] ) self.tmp_containers.append(ctnr) @@ -655,7 +650,7 @@ class ArchiveTest(BaseAPIIntegrationTest): def test_get_file_stat_from_container(self): data = 'The Maid and the Pocket Watch of Blood' ctnr = self.client.create_container( - BUSYBOX, 'sh -c "echo -n {0} > /vol1/data.txt"'.format(data), + TEST_IMG, 'sh -c "echo -n {0} > /vol1/data.txt"'.format(data), volumes=['/vol1'] ) self.tmp_containers.append(ctnr) @@ -673,7 +668,7 @@ class ArchiveTest(BaseAPIIntegrationTest): test_file.write(data) test_file.seek(0) ctnr = self.client.create_container( - BUSYBOX, + TEST_IMG, 'cat {0}'.format( os.path.join('/vol1/', os.path.basename(test_file.name)) ), @@ -695,7 +690,7 @@ class ArchiveTest(BaseAPIIntegrationTest): dirs = ['foo', 'bar'] base = helpers.make_tree(dirs, files) ctnr = self.client.create_container( - BUSYBOX, 'ls -p /vol1', volumes=['/vol1'] + TEST_IMG, 'ls -p /vol1', volumes=['/vol1'] ) self.tmp_containers.append(ctnr) with docker.utils.tar(base) as test_tar: @@ -716,7 +711,7 @@ class RenameContainerTest(BaseAPIIntegrationTest): def test_rename_container(self): version = self.client.version()['Version'] name = 'hong_meiling' - res = self.client.create_container(BUSYBOX, 'true') + res = self.client.create_container(TEST_IMG, 'true') assert 'Id' in res self.tmp_containers.append(res['Id']) self.client.rename(res, name) @@ -730,7 +725,7 @@ class RenameContainerTest(BaseAPIIntegrationTest): class StartContainerTest(BaseAPIIntegrationTest): def test_start_container(self): - res = self.client.create_container(BUSYBOX, 'true') + res = self.client.create_container(TEST_IMG, 'true') assert 'Id' in res self.tmp_containers.append(res['Id']) self.client.start(res['Id']) @@ -746,7 +741,7 @@ class StartContainerTest(BaseAPIIntegrationTest): assert inspect['State']['ExitCode'] == 0 def test_start_container_with_dict_instead_of_id(self): - res = self.client.create_container(BUSYBOX, 'true') + res = self.client.create_container(TEST_IMG, 'true') assert 'Id' in res self.tmp_containers.append(res['Id']) self.client.start(res) @@ -774,7 +769,7 @@ class StartContainerTest(BaseAPIIntegrationTest): 'true && echo "Night of Nights"' ] for cmd in commands: - container = self.client.create_container(BUSYBOX, cmd) + container = self.client.create_container(TEST_IMG, cmd) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) @@ -784,7 +779,7 @@ class StartContainerTest(BaseAPIIntegrationTest): class WaitTest(BaseAPIIntegrationTest): def test_wait(self): - res = self.client.create_container(BUSYBOX, ['sleep', '3']) + res = self.client.create_container(TEST_IMG, ['sleep', '3']) id = res['Id'] self.tmp_containers.append(id) self.client.start(id) @@ -797,7 +792,7 @@ class WaitTest(BaseAPIIntegrationTest): assert inspect['State']['ExitCode'] == exitcode def test_wait_with_dict_instead_of_id(self): - res = self.client.create_container(BUSYBOX, ['sleep', '3']) + res = self.client.create_container(TEST_IMG, ['sleep', '3']) id = res['Id'] self.tmp_containers.append(id) self.client.start(res) @@ -811,13 +806,13 @@ class WaitTest(BaseAPIIntegrationTest): @requires_api_version('1.30') def test_wait_with_condition(self): - ctnr = self.client.create_container(BUSYBOX, 'true') + ctnr = self.client.create_container(TEST_IMG, 'true') self.tmp_containers.append(ctnr) with pytest.raises(requests.exceptions.ConnectionError): self.client.wait(ctnr, condition='removed', timeout=1) ctnr = self.client.create_container( - BUSYBOX, ['sleep', '3'], + TEST_IMG, ['sleep', '3'], host_config=self.client.create_host_config(auto_remove=True) ) self.tmp_containers.append(ctnr) @@ -831,7 +826,7 @@ class LogsTest(BaseAPIIntegrationTest): def test_logs(self): snippet = 'Flowering Nights (Sakuya Iyazoi)' container = self.client.create_container( - BUSYBOX, 'echo {0}'.format(snippet) + TEST_IMG, 'echo {0}'.format(snippet) ) id = container['Id'] self.tmp_containers.append(id) @@ -845,7 +840,7 @@ class LogsTest(BaseAPIIntegrationTest): snippet = '''Line1 Line2''' container = self.client.create_container( - BUSYBOX, 'echo "{0}"'.format(snippet) + TEST_IMG, 'echo "{0}"'.format(snippet) ) id = container['Id'] self.tmp_containers.append(id) @@ -858,7 +853,7 @@ Line2''' def test_logs_streaming_and_follow(self): snippet = 'Flowering Nights (Sakuya Iyazoi)' container = self.client.create_container( - BUSYBOX, 'echo {0}'.format(snippet) + TEST_IMG, 'echo {0}'.format(snippet) ) id = container['Id'] self.tmp_containers.append(id) @@ -873,10 +868,12 @@ Line2''' assert logs == (snippet + '\n').encode(encoding='ascii') @pytest.mark.timeout(5) + @pytest.mark.skipif(os.environ.get('DOCKER_HOST', '').startswith('ssh://'), + reason='No cancellable streams over SSH') def test_logs_streaming_and_follow_and_cancel(self): snippet = 'Flowering Nights (Sakuya Iyazoi)' container = self.client.create_container( - BUSYBOX, 'sh -c "echo \\"{0}\\" && sleep 3"'.format(snippet) + TEST_IMG, 'sh -c "echo \\"{0}\\" && sleep 3"'.format(snippet) ) id = container['Id'] self.tmp_containers.append(id) @@ -894,7 +891,7 @@ Line2''' def test_logs_with_dict_instead_of_id(self): snippet = 'Flowering Nights (Sakuya Iyazoi)' container = self.client.create_container( - BUSYBOX, 'echo {0}'.format(snippet) + TEST_IMG, 'echo {0}'.format(snippet) ) id = container['Id'] self.tmp_containers.append(id) @@ -907,7 +904,7 @@ Line2''' def test_logs_with_tail_0(self): snippet = 'Flowering Nights (Sakuya Iyazoi)' container = self.client.create_container( - BUSYBOX, 'echo "{0}"'.format(snippet) + TEST_IMG, 'echo "{0}"'.format(snippet) ) id = container['Id'] self.tmp_containers.append(id) @@ -921,7 +918,7 @@ Line2''' def test_logs_with_until(self): snippet = 'Shanghai Teahouse (Hong Meiling)' container = self.client.create_container( - BUSYBOX, 'echo "{0}"'.format(snippet) + TEST_IMG, 'echo "{0}"'.format(snippet) ) self.tmp_containers.append(container) @@ -936,7 +933,7 @@ Line2''' class DiffTest(BaseAPIIntegrationTest): def test_diff(self): - container = self.client.create_container(BUSYBOX, ['touch', '/test']) + container = self.client.create_container(TEST_IMG, ['touch', '/test']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) @@ -949,7 +946,7 @@ class DiffTest(BaseAPIIntegrationTest): assert test_diff[0]['Kind'] == 1 def test_diff_with_dict_instead_of_id(self): - container = self.client.create_container(BUSYBOX, ['touch', '/test']) + container = self.client.create_container(TEST_IMG, ['touch', '/test']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) @@ -964,7 +961,7 @@ class DiffTest(BaseAPIIntegrationTest): class StopTest(BaseAPIIntegrationTest): def test_stop(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + container = self.client.create_container(TEST_IMG, ['sleep', '9999']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) @@ -976,7 +973,7 @@ class StopTest(BaseAPIIntegrationTest): assert state['Running'] is False def test_stop_with_dict_instead_of_id(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + container = self.client.create_container(TEST_IMG, ['sleep', '9999']) assert 'Id' in container id = container['Id'] self.client.start(container) @@ -991,7 +988,7 @@ class StopTest(BaseAPIIntegrationTest): class KillTest(BaseAPIIntegrationTest): def test_kill(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + container = self.client.create_container(TEST_IMG, ['sleep', '9999']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) @@ -1005,7 +1002,7 @@ class KillTest(BaseAPIIntegrationTest): assert state['Running'] is False def test_kill_with_dict_instead_of_id(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + container = self.client.create_container(TEST_IMG, ['sleep', '9999']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) @@ -1019,7 +1016,7 @@ class KillTest(BaseAPIIntegrationTest): assert state['Running'] is False def test_kill_with_signal(self): - id = self.client.create_container(BUSYBOX, ['sleep', '60']) + id = self.client.create_container(TEST_IMG, ['sleep', '60']) self.tmp_containers.append(id) self.client.start(id) self.client.kill( @@ -1036,7 +1033,7 @@ class KillTest(BaseAPIIntegrationTest): assert state['Running'] is False, state def test_kill_with_signal_name(self): - id = self.client.create_container(BUSYBOX, ['sleep', '60']) + id = self.client.create_container(TEST_IMG, ['sleep', '60']) self.client.start(id) self.tmp_containers.append(id) self.client.kill(id, signal='SIGKILL') @@ -1051,7 +1048,7 @@ class KillTest(BaseAPIIntegrationTest): assert state['Running'] is False, state def test_kill_with_signal_integer(self): - id = self.client.create_container(BUSYBOX, ['sleep', '60']) + id = self.client.create_container(TEST_IMG, ['sleep', '60']) self.client.start(id) self.tmp_containers.append(id) self.client.kill(id, signal=9) @@ -1068,14 +1065,19 @@ class KillTest(BaseAPIIntegrationTest): class PortTest(BaseAPIIntegrationTest): def test_port(self): - port_bindings = { '1111': ('127.0.0.1', '4567'), - '2222': ('127.0.0.1', '4568') + '2222': ('127.0.0.1', '4568'), + '3333/udp': ('127.0.0.1', '4569'), } + ports = [ + 1111, + 2222, + (3333, 'udp'), + ] container = self.client.create_container( - BUSYBOX, ['sleep', '60'], ports=list(port_bindings.keys()), + TEST_IMG, ['sleep', '60'], ports=ports, host_config=self.client.create_host_config( port_bindings=port_bindings, network_mode='bridge' ) @@ -1086,13 +1088,15 @@ class PortTest(BaseAPIIntegrationTest): # Call the port function on each biding and compare expected vs actual for port in port_bindings: + port, _, protocol = port.partition('/') actual_bindings = self.client.port(container, port) port_binding = actual_bindings.pop() ip, host_port = port_binding['HostIp'], port_binding['HostPort'] - assert ip == port_bindings[port][0] - assert host_port == port_bindings[port][1] + port_binding = port if not protocol else port + "/" + protocol + assert ip == port_bindings[port_binding][0] + assert host_port == port_bindings[port_binding][1] self.client.kill(id) @@ -1100,7 +1104,7 @@ class PortTest(BaseAPIIntegrationTest): class ContainerTopTest(BaseAPIIntegrationTest): def test_top(self): container = self.client.create_container( - BUSYBOX, ['sleep', '60'] + TEST_IMG, ['sleep', '60'] ) self.tmp_containers.append(container) @@ -1120,7 +1124,7 @@ class ContainerTopTest(BaseAPIIntegrationTest): ) def test_top_with_psargs(self): container = self.client.create_container( - BUSYBOX, ['sleep', '60']) + TEST_IMG, ['sleep', '60']) self.tmp_containers.append(container) @@ -1136,7 +1140,7 @@ class ContainerTopTest(BaseAPIIntegrationTest): class RestartContainerTest(BaseAPIIntegrationTest): def test_restart(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + container = self.client.create_container(TEST_IMG, ['sleep', '9999']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) @@ -1155,16 +1159,16 @@ class RestartContainerTest(BaseAPIIntegrationTest): self.client.kill(id) def test_restart_with_low_timeout(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + container = self.client.create_container(TEST_IMG, ['sleep', '9999']) self.client.start(container) - self.client.timeout = 1 - self.client.restart(container, timeout=3) + self.client.timeout = 3 + self.client.restart(container, timeout=1) self.client.timeout = None - self.client.restart(container, timeout=3) + self.client.restart(container, timeout=1) self.client.kill(container) def test_restart_with_dict_instead_of_id(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + container = self.client.create_container(TEST_IMG, ['sleep', '9999']) assert 'Id' in container id = container['Id'] self.client.start(container) @@ -1186,7 +1190,7 @@ class RestartContainerTest(BaseAPIIntegrationTest): class RemoveContainerTest(BaseAPIIntegrationTest): def test_remove(self): - container = self.client.create_container(BUSYBOX, ['true']) + container = self.client.create_container(TEST_IMG, ['true']) id = container['Id'] self.client.start(id) self.client.wait(id) @@ -1196,7 +1200,7 @@ class RemoveContainerTest(BaseAPIIntegrationTest): assert len(res) == 0 def test_remove_with_dict_instead_of_id(self): - container = self.client.create_container(BUSYBOX, ['true']) + container = self.client.create_container(TEST_IMG, ['true']) id = container['Id'] self.client.start(id) self.client.wait(id) @@ -1208,7 +1212,7 @@ class RemoveContainerTest(BaseAPIIntegrationTest): class AttachContainerTest(BaseAPIIntegrationTest): def test_run_container_streaming(self): - container = self.client.create_container(BUSYBOX, '/bin/sh', + container = self.client.create_container(TEST_IMG, '/bin/sh', detach=True, stdin_open=True) id = container['Id'] self.tmp_containers.append(id) @@ -1220,7 +1224,7 @@ class AttachContainerTest(BaseAPIIntegrationTest): line = 'hi there and stuff and things, words!' # `echo` appends CRLF, `printf` doesn't command = "printf '{0}'".format(line) - container = self.client.create_container(BUSYBOX, command, + container = self.client.create_container(TEST_IMG, command, detach=True, tty=False) self.tmp_containers.append(container) @@ -1230,31 +1234,37 @@ class AttachContainerTest(BaseAPIIntegrationTest): self.client.start(container) - next_size = next_frame_size(pty_stdout) + (stream, next_size) = next_frame_header(pty_stdout) + assert stream == 1 # correspond to stdout assert next_size == len(line) data = read_exactly(pty_stdout, next_size) assert data.decode('utf-8') == line def test_attach_no_stream(self): container = self.client.create_container( - BUSYBOX, 'echo hello' + TEST_IMG, 'echo hello' ) self.tmp_containers.append(container) self.client.start(container) output = self.client.attach(container, stream=False, logs=True) assert output == 'hello\n'.encode(encoding='ascii') - @pytest.mark.timeout(5) + @pytest.mark.timeout(10) + @pytest.mark.skipif(os.environ.get('DOCKER_HOST', '').startswith('ssh://'), + reason='No cancellable streams over SSH') + @pytest.mark.xfail(condition=os.environ.get('DOCKER_TLS_VERIFY') or + os.environ.get('DOCKER_CERT_PATH'), + reason='Flaky test on TLS') def test_attach_stream_and_cancel(self): container = self.client.create_container( - BUSYBOX, 'sh -c "echo hello && sleep 60"', + TEST_IMG, 'sh -c "sleep 2 && echo hello && sleep 60"', tty=True ) self.tmp_containers.append(container) self.client.start(container) output = self.client.attach(container, stream=True, logs=True) - threading.Timer(1, output.close).start() + threading.Timer(3, output.close).start() lines = [] for line in output: @@ -1265,7 +1275,7 @@ class AttachContainerTest(BaseAPIIntegrationTest): def test_detach_with_default(self): container = self.client.create_container( - BUSYBOX, 'cat', + TEST_IMG, 'cat', detach=True, stdin_open=True, tty=True ) self.tmp_containers.append(container) @@ -1284,7 +1294,7 @@ class AttachContainerTest(BaseAPIIntegrationTest): self.client._general_configs['detachKeys'] = 'ctrl-p' container = self.client.create_container( - BUSYBOX, 'cat', + TEST_IMG, 'cat', detach=True, stdin_open=True, tty=True ) self.tmp_containers.append(container) @@ -1301,7 +1311,7 @@ class AttachContainerTest(BaseAPIIntegrationTest): self.client._general_configs['detachKeys'] = 'ctrl-p' container = self.client.create_container( - BUSYBOX, 'cat', + TEST_IMG, 'cat', detach=True, stdin_open=True, tty=True ) self.tmp_containers.append(container) @@ -1317,7 +1327,7 @@ class AttachContainerTest(BaseAPIIntegrationTest): class PauseTest(BaseAPIIntegrationTest): def test_pause_unpause(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + container = self.client.create_container(TEST_IMG, ['sleep', '9999']) id = container['Id'] self.tmp_containers.append(id) self.client.start(container) @@ -1348,9 +1358,9 @@ class PruneTest(BaseAPIIntegrationTest): @requires_api_version('1.25') def test_prune_containers(self): container1 = self.client.create_container( - BUSYBOX, ['sh', '-c', 'echo hello > /data.txt'] + TEST_IMG, ['sh', '-c', 'echo hello > /data.txt'] ) - container2 = self.client.create_container(BUSYBOX, ['sleep', '9999']) + container2 = self.client.create_container(TEST_IMG, ['sleep', '9999']) self.client.start(container1) self.client.start(container2) self.client.wait(container1) @@ -1363,7 +1373,7 @@ class PruneTest(BaseAPIIntegrationTest): class GetContainerStatsTest(BaseAPIIntegrationTest): def test_get_container_stats_no_stream(self): container = self.client.create_container( - BUSYBOX, ['sleep', '60'], + TEST_IMG, ['sleep', '60'], ) self.tmp_containers.append(container) self.client.start(container) @@ -1377,7 +1387,7 @@ class GetContainerStatsTest(BaseAPIIntegrationTest): def test_get_container_stats_stream(self): container = self.client.create_container( - BUSYBOX, ['sleep', '60'], + TEST_IMG, ['sleep', '60'], ) self.tmp_containers.append(container) self.client.start(container) @@ -1395,7 +1405,7 @@ class ContainerUpdateTest(BaseAPIIntegrationTest): old_mem_limit = 400 * 1024 * 1024 new_mem_limit = 300 * 1024 * 1024 container = self.client.create_container( - BUSYBOX, 'top', host_config=self.client.create_host_config( + TEST_IMG, 'top', host_config=self.client.create_host_config( mem_limit=old_mem_limit ) ) @@ -1416,7 +1426,7 @@ class ContainerUpdateTest(BaseAPIIntegrationTest): 'Name': 'on-failure' } container = self.client.create_container( - BUSYBOX, ['sleep', '60'], + TEST_IMG, ['sleep', '60'], host_config=self.client.create_host_config( restart_policy=old_restart_policy ) @@ -1440,7 +1450,7 @@ class ContainerCPUTest(BaseAPIIntegrationTest): def test_container_cpu_shares(self): cpu_shares = 512 container = self.client.create_container( - BUSYBOX, 'ls', host_config=self.client.create_host_config( + TEST_IMG, 'ls', host_config=self.client.create_host_config( cpu_shares=cpu_shares ) ) @@ -1452,7 +1462,7 @@ class ContainerCPUTest(BaseAPIIntegrationTest): def test_container_cpuset(self): cpuset_cpus = "0,1" container = self.client.create_container( - BUSYBOX, 'ls', host_config=self.client.create_host_config( + TEST_IMG, 'ls', host_config=self.client.create_host_config( cpuset_cpus=cpuset_cpus ) ) @@ -1464,7 +1474,7 @@ class ContainerCPUTest(BaseAPIIntegrationTest): @requires_api_version('1.25') def test_create_with_runtime(self): container = self.client.create_container( - BUSYBOX, ['echo', 'test'], runtime='runc' + TEST_IMG, ['echo', 'test'], runtime='runc' ) self.tmp_containers.append(container['Id']) config = self.client.inspect_container(container) @@ -1475,7 +1485,7 @@ class LinkTest(BaseAPIIntegrationTest): def test_remove_link(self): # Create containers container1 = self.client.create_container( - BUSYBOX, 'cat', detach=True, stdin_open=True + TEST_IMG, 'cat', detach=True, stdin_open=True ) container1_id = container1['Id'] self.tmp_containers.append(container1_id) @@ -1487,7 +1497,7 @@ class LinkTest(BaseAPIIntegrationTest): link_alias = 'mylink' container2 = self.client.create_container( - BUSYBOX, 'cat', host_config=self.client.create_host_config( + TEST_IMG, 'cat', host_config=self.client.create_host_config( links={link_path: link_alias} ) ) diff --git a/tests/integration/api_exec_test.py b/tests/integration/api_exec_test.py index 1a5a4e5..554e862 100644 --- a/tests/integration/api_exec_test.py +++ b/tests/integration/api_exec_test.py @@ -1,15 +1,54 @@ -from docker.utils.socket import next_frame_size +from ..helpers import assert_cat_socket_detached_with_keys +from ..helpers import ctrl_with +from ..helpers import requires_api_version +from .base import BaseAPIIntegrationTest +from .base import TEST_IMG +from docker.utils.proxy import ProxyConfig +from docker.utils.socket import next_frame_header from docker.utils.socket import read_exactly -from .base import BaseAPIIntegrationTest, BUSYBOX -from ..helpers import ( - requires_api_version, ctrl_with, assert_cat_socket_detached_with_keys -) - class ExecTest(BaseAPIIntegrationTest): + def test_execute_command_with_proxy_env(self): + # Set a custom proxy config on the client + self.client._proxy_configs = ProxyConfig( + ftp='a', https='b', http='c', no_proxy='d' + ) + + container = self.client.create_container( + TEST_IMG, 'cat', detach=True, stdin_open=True, + ) + self.client.start(container) + self.tmp_containers.append(container) + + cmd = 'sh -c "env | grep -i proxy"' + + # First, just make sure the environment variables from the custom + # config are set + + res = self.client.exec_create(container, cmd=cmd) + output = self.client.exec_start(res).decode('utf-8').split('\n') + expected = [ + 'ftp_proxy=a', 'https_proxy=b', 'http_proxy=c', 'no_proxy=d', + 'FTP_PROXY=a', 'HTTPS_PROXY=b', 'HTTP_PROXY=c', 'NO_PROXY=d' + ] + for item in expected: + assert item in output + + # Overwrite some variables with a custom environment + env = {'https_proxy': 'xxx', 'HTTPS_PROXY': 'XXX'} + + res = self.client.exec_create(container, cmd=cmd, environment=env) + output = self.client.exec_start(res).decode('utf-8').split('\n') + expected = [ + 'ftp_proxy=a', 'https_proxy=xxx', 'http_proxy=c', 'no_proxy=d', + 'FTP_PROXY=a', 'HTTPS_PROXY=XXX', 'HTTP_PROXY=c', 'NO_PROXY=d' + ] + for item in expected: + assert item in output + def test_execute_command(self): - container = self.client.create_container(BUSYBOX, 'cat', + container = self.client.create_container(TEST_IMG, 'cat', detach=True, stdin_open=True) id = container['Id'] self.client.start(id) @@ -22,7 +61,7 @@ class ExecTest(BaseAPIIntegrationTest): assert exec_log == b'hello\n' def test_exec_command_string(self): - container = self.client.create_container(BUSYBOX, 'cat', + container = self.client.create_container(TEST_IMG, 'cat', detach=True, stdin_open=True) id = container['Id'] self.client.start(id) @@ -35,20 +74,20 @@ class ExecTest(BaseAPIIntegrationTest): assert exec_log == b'hello world\n' def test_exec_command_as_user(self): - container = self.client.create_container(BUSYBOX, 'cat', + container = self.client.create_container(TEST_IMG, 'cat', detach=True, stdin_open=True) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) - res = self.client.exec_create(id, 'whoami', user='default') + res = self.client.exec_create(id, 'whoami', user='postgres') assert 'Id' in res exec_log = self.client.exec_start(res) - assert exec_log == b'default\n' + assert exec_log == b'postgres\n' def test_exec_command_as_root(self): - container = self.client.create_container(BUSYBOX, 'cat', + container = self.client.create_container(TEST_IMG, 'cat', detach=True, stdin_open=True) id = container['Id'] self.client.start(id) @@ -61,7 +100,7 @@ class ExecTest(BaseAPIIntegrationTest): assert exec_log == b'root\n' def test_exec_command_streaming(self): - container = self.client.create_container(BUSYBOX, 'cat', + container = self.client.create_container(TEST_IMG, 'cat', detach=True, stdin_open=True) id = container['Id'] self.tmp_containers.append(id) @@ -76,7 +115,7 @@ class ExecTest(BaseAPIIntegrationTest): assert res == b'hello\nworld\n' def test_exec_start_socket(self): - container = self.client.create_container(BUSYBOX, 'cat', + container = self.client.create_container(TEST_IMG, 'cat', detach=True, stdin_open=True) container_id = container['Id'] self.client.start(container_id) @@ -91,13 +130,14 @@ class ExecTest(BaseAPIIntegrationTest): socket = self.client.exec_start(exec_id, socket=True) self.addCleanup(socket.close) - next_size = next_frame_size(socket) + (stream, next_size) = next_frame_header(socket) + assert stream == 1 # stdout (0 = stdin, 1 = stdout, 2 = stderr) assert next_size == len(line) data = read_exactly(socket, next_size) assert data.decode('utf-8') == line def test_exec_start_detached(self): - container = self.client.create_container(BUSYBOX, 'cat', + container = self.client.create_container(TEST_IMG, 'cat', detach=True, stdin_open=True) container_id = container['Id'] self.client.start(container_id) @@ -112,7 +152,7 @@ class ExecTest(BaseAPIIntegrationTest): assert response == "" def test_exec_inspect(self): - container = self.client.create_container(BUSYBOX, 'cat', + container = self.client.create_container(TEST_IMG, 'cat', detach=True, stdin_open=True) id = container['Id'] self.client.start(id) @@ -127,7 +167,7 @@ class ExecTest(BaseAPIIntegrationTest): @requires_api_version('1.25') def test_exec_command_with_env(self): - container = self.client.create_container(BUSYBOX, 'cat', + container = self.client.create_container(TEST_IMG, 'cat', detach=True, stdin_open=True) id = container['Id'] self.client.start(id) @@ -142,18 +182,18 @@ class ExecTest(BaseAPIIntegrationTest): @requires_api_version('1.35') def test_exec_command_with_workdir(self): container = self.client.create_container( - BUSYBOX, 'cat', detach=True, stdin_open=True + TEST_IMG, 'cat', detach=True, stdin_open=True ) self.tmp_containers.append(container) self.client.start(container) - res = self.client.exec_create(container, 'pwd', workdir='/var/www') + res = self.client.exec_create(container, 'pwd', workdir='/var/opt') exec_log = self.client.exec_start(res) - assert exec_log == b'/var/www\n' + assert exec_log == b'/var/opt\n' def test_detach_with_default(self): container = self.client.create_container( - BUSYBOX, 'cat', detach=True, stdin_open=True + TEST_IMG, 'cat', detach=True, stdin_open=True ) id = container['Id'] self.client.start(id) @@ -172,7 +212,7 @@ class ExecTest(BaseAPIIntegrationTest): def test_detach_with_config_file(self): self.client._general_configs['detachKeys'] = 'ctrl-p' container = self.client.create_container( - BUSYBOX, 'cat', detach=True, stdin_open=True + TEST_IMG, 'cat', detach=True, stdin_open=True ) id = container['Id'] self.client.start(id) @@ -186,20 +226,87 @@ class ExecTest(BaseAPIIntegrationTest): assert_cat_socket_detached_with_keys(sock, [ctrl_with('p')]) - def test_detach_with_arg(self): - self.client._general_configs['detachKeys'] = 'ctrl-p' - container = self.client.create_container( - BUSYBOX, 'cat', detach=True, stdin_open=True - ) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - exec_id = self.client.exec_create( - id, 'cat', - stdin=True, tty=True, detach_keys='ctrl-x', stdout=True +class ExecDemuxTest(BaseAPIIntegrationTest): + cmd = 'sh -c "{}"'.format(' ; '.join([ + # Write something on stdout + 'echo hello out', + # Busybox's sleep does not handle sub-second times. + # This loops takes ~0.3 second to execute on my machine. + 'sleep 0.5', + # Write something on stderr + 'echo hello err >&2']) + ) + + def setUp(self): + super(ExecDemuxTest, self).setUp() + self.container = self.client.create_container( + TEST_IMG, 'cat', detach=True, stdin_open=True ) - sock = self.client.exec_start(exec_id, tty=True, socket=True) - self.addCleanup(sock.close) + self.client.start(self.container) + self.tmp_containers.append(self.container) - assert_cat_socket_detached_with_keys(sock, [ctrl_with('x')]) + def test_exec_command_no_stream_no_demux(self): + # tty=False, stream=False, demux=False + res = self.client.exec_create(self.container, self.cmd) + exec_log = self.client.exec_start(res) + assert b'hello out\n' in exec_log + assert b'hello err\n' in exec_log + + def test_exec_command_stream_no_demux(self): + # tty=False, stream=True, demux=False + res = self.client.exec_create(self.container, self.cmd) + exec_log = list(self.client.exec_start(res, stream=True)) + assert len(exec_log) == 2 + assert b'hello out\n' in exec_log + assert b'hello err\n' in exec_log + + def test_exec_command_no_stream_demux(self): + # tty=False, stream=False, demux=True + res = self.client.exec_create(self.container, self.cmd) + exec_log = self.client.exec_start(res, demux=True) + assert exec_log == (b'hello out\n', b'hello err\n') + + def test_exec_command_stream_demux(self): + # tty=False, stream=True, demux=True + res = self.client.exec_create(self.container, self.cmd) + exec_log = list(self.client.exec_start(res, demux=True, stream=True)) + assert len(exec_log) == 2 + assert (b'hello out\n', None) in exec_log + assert (None, b'hello err\n') in exec_log + + def test_exec_command_tty_no_stream_no_demux(self): + # tty=True, stream=False, demux=False + res = self.client.exec_create(self.container, self.cmd, tty=True) + exec_log = self.client.exec_start(res) + assert exec_log == b'hello out\r\nhello err\r\n' + + def test_exec_command_tty_stream_no_demux(self): + # tty=True, stream=True, demux=False + res = self.client.exec_create(self.container, self.cmd, tty=True) + exec_log = list(self.client.exec_start(res, stream=True)) + assert b'hello out\r\n' in exec_log + if len(exec_log) == 2: + assert b'hello err\r\n' in exec_log + else: + assert len(exec_log) == 3 + assert b'hello err' in exec_log + assert b'\r\n' in exec_log + + def test_exec_command_tty_no_stream_demux(self): + # tty=True, stream=False, demux=True + res = self.client.exec_create(self.container, self.cmd, tty=True) + exec_log = self.client.exec_start(res, demux=True) + assert exec_log == (b'hello out\r\nhello err\r\n', None) + + def test_exec_command_tty_stream_demux(self): + # tty=True, stream=True, demux=True + res = self.client.exec_create(self.container, self.cmd, tty=True) + exec_log = list(self.client.exec_start(res, demux=True, stream=True)) + assert (b'hello out\r\n', None) in exec_log + if len(exec_log) == 2: + assert (b'hello err\r\n', None) in exec_log + else: + assert len(exec_log) == 3 + assert (b'hello err', None) in exec_log + assert (b'\r\n', None) in exec_log diff --git a/tests/integration/api_healthcheck_test.py b/tests/integration/api_healthcheck_test.py index 5dbac37..c54583b 100644 --- a/tests/integration/api_healthcheck_test.py +++ b/tests/integration/api_healthcheck_test.py @@ -1,4 +1,4 @@ -from .base import BaseAPIIntegrationTest, BUSYBOX +from .base import BaseAPIIntegrationTest, TEST_IMG from .. import helpers SECOND = 1000000000 @@ -16,7 +16,7 @@ class HealthcheckTest(BaseAPIIntegrationTest): @helpers.requires_api_version('1.24') def test_healthcheck_shell_command(self): container = self.client.create_container( - BUSYBOX, 'top', healthcheck=dict(test='echo "hello world"')) + TEST_IMG, 'top', healthcheck=dict(test='echo "hello world"')) self.tmp_containers.append(container) res = self.client.inspect_container(container) @@ -27,7 +27,7 @@ class HealthcheckTest(BaseAPIIntegrationTest): @helpers.requires_api_version('1.24') def test_healthcheck_passes(self): container = self.client.create_container( - BUSYBOX, 'top', healthcheck=dict( + TEST_IMG, 'top', healthcheck=dict( test="true", interval=1 * SECOND, timeout=1 * SECOND, @@ -40,7 +40,7 @@ class HealthcheckTest(BaseAPIIntegrationTest): @helpers.requires_api_version('1.24') def test_healthcheck_fails(self): container = self.client.create_container( - BUSYBOX, 'top', healthcheck=dict( + TEST_IMG, 'top', healthcheck=dict( test="false", interval=1 * SECOND, timeout=1 * SECOND, @@ -53,7 +53,7 @@ class HealthcheckTest(BaseAPIIntegrationTest): @helpers.requires_api_version('1.29') def test_healthcheck_start_period(self): container = self.client.create_container( - BUSYBOX, 'top', healthcheck=dict( + TEST_IMG, 'top', healthcheck=dict( test="echo 'x' >> /counter.txt && " "test `cat /counter.txt | wc -l` -ge 3", interval=1 * SECOND, diff --git a/tests/integration/api_image_test.py b/tests/integration/api_image_test.py index 050e7f3..2bc96ab 100644 --- a/tests/integration/api_image_test.py +++ b/tests/integration/api_image_test.py @@ -15,7 +15,7 @@ from six.moves import socketserver import docker from ..helpers import requires_api_version, requires_experimental -from .base import BaseAPIIntegrationTest, BUSYBOX +from .base import BaseAPIIntegrationTest, TEST_IMG class ListImagesTest(BaseAPIIntegrationTest): @@ -69,13 +69,15 @@ class PullImageTest(BaseAPIIntegrationTest): with pytest.raises(docker.errors.APIError) as excinfo: self.client.pull('hello-world', platform='foobar') - assert excinfo.value.status_code == 500 - assert 'invalid platform' in excinfo.exconly() + # Some API versions incorrectly returns 500 status; assert 4xx or 5xx + assert excinfo.value.is_error() + assert 'unknown operating system' in excinfo.exconly() \ + or 'invalid platform' in excinfo.exconly() class CommitTest(BaseAPIIntegrationTest): def test_commit(self): - container = self.client.create_container(BUSYBOX, ['touch', '/test']) + container = self.client.create_container(TEST_IMG, ['touch', '/test']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) @@ -88,13 +90,13 @@ class CommitTest(BaseAPIIntegrationTest): assert img['Container'].startswith(id) assert 'ContainerConfig' in img assert 'Image' in img['ContainerConfig'] - assert BUSYBOX == img['ContainerConfig']['Image'] - busybox_id = self.client.inspect_image(BUSYBOX)['Id'] + assert TEST_IMG == img['ContainerConfig']['Image'] + busybox_id = self.client.inspect_image(TEST_IMG)['Id'] assert 'Parent' in img assert img['Parent'] == busybox_id def test_commit_with_changes(self): - cid = self.client.create_container(BUSYBOX, ['touch', '/test']) + cid = self.client.create_container(TEST_IMG, ['touch', '/test']) self.tmp_containers.append(cid) self.client.start(cid) img_id = self.client.commit( @@ -110,7 +112,7 @@ class CommitTest(BaseAPIIntegrationTest): class RemoveImageTest(BaseAPIIntegrationTest): def test_remove(self): - container = self.client.create_container(BUSYBOX, ['touch', '/test']) + container = self.client.create_container(TEST_IMG, ['touch', '/test']) id = container['Id'] self.client.start(id) self.tmp_containers.append(id) @@ -317,7 +319,7 @@ class PruneImagesTest(BaseAPIIntegrationTest): pass # Ensure busybox does not get pruned - ctnr = self.client.create_container(BUSYBOX, ['sleep', '9999']) + ctnr = self.client.create_container(TEST_IMG, ['sleep', '9999']) self.tmp_containers.append(ctnr) self.client.pull('hello-world', tag='latest') @@ -341,7 +343,7 @@ class SaveLoadImagesTest(BaseAPIIntegrationTest): @requires_api_version('1.23') def test_get_image_load_image(self): with tempfile.TemporaryFile() as f: - stream = self.client.get_image(BUSYBOX) + stream = self.client.get_image(TEST_IMG) for chunk in stream: f.write(chunk) @@ -349,7 +351,7 @@ class SaveLoadImagesTest(BaseAPIIntegrationTest): result = self.client.load_image(f.read()) success = False - result_line = 'Loaded image: {}\n'.format(BUSYBOX) + result_line = 'Loaded image: {}\n'.format(TEST_IMG) for data in result: print(data) if 'stream' in data: diff --git a/tests/integration/api_network_test.py b/tests/integration/api_network_test.py index b6726d0..0f26827 100644 --- a/tests/integration/api_network_test.py +++ b/tests/integration/api_network_test.py @@ -3,13 +3,13 @@ from docker.types import IPAMConfig, IPAMPool import pytest from ..helpers import random_name, requires_api_version -from .base import BaseAPIIntegrationTest, BUSYBOX +from .base import BaseAPIIntegrationTest, TEST_IMG class TestNetworks(BaseAPIIntegrationTest): def tearDown(self): - super(TestNetworks, self).tearDown() self.client.leave_swarm(force=True) + super(TestNetworks, self).tearDown() def create_network(self, *args, **kwargs): net_name = random_name() @@ -92,7 +92,7 @@ class TestNetworks(BaseAPIIntegrationTest): def test_connect_and_disconnect_container(self): net_name, net_id = self.create_network() - container = self.client.create_container(BUSYBOX, 'top') + container = self.client.create_container(TEST_IMG, 'top') self.tmp_containers.append(container) self.client.start(container) @@ -119,7 +119,7 @@ class TestNetworks(BaseAPIIntegrationTest): def test_connect_and_force_disconnect_container(self): net_name, net_id = self.create_network() - container = self.client.create_container(BUSYBOX, 'top') + container = self.client.create_container(TEST_IMG, 'top') self.tmp_containers.append(container) self.client.start(container) @@ -144,7 +144,7 @@ class TestNetworks(BaseAPIIntegrationTest): def test_connect_with_aliases(self): net_name, net_id = self.create_network() - container = self.client.create_container(BUSYBOX, 'top') + container = self.client.create_container(TEST_IMG, 'top') self.tmp_containers.append(container) self.client.start(container) @@ -161,7 +161,7 @@ class TestNetworks(BaseAPIIntegrationTest): net_name, net_id = self.create_network() container = self.client.create_container( - image=BUSYBOX, + image=TEST_IMG, command='top', host_config=self.client.create_host_config(network_mode=net_name), ) @@ -181,7 +181,7 @@ class TestNetworks(BaseAPIIntegrationTest): net_name, net_id = self.create_network() container = self.client.create_container( - image=BUSYBOX, + image=TEST_IMG, command='top', host_config=self.client.create_host_config( network_mode=net_name, @@ -211,7 +211,7 @@ class TestNetworks(BaseAPIIntegrationTest): ), ) container = self.client.create_container( - image=BUSYBOX, command='top', + image=TEST_IMG, command='top', host_config=self.client.create_host_config(network_mode=net_name), networking_config=self.client.create_networking_config({ net_name: self.client.create_endpoint_config( @@ -237,7 +237,7 @@ class TestNetworks(BaseAPIIntegrationTest): ), ) container = self.client.create_container( - image=BUSYBOX, command='top', + image=TEST_IMG, command='top', host_config=self.client.create_host_config(network_mode=net_name), networking_config=self.client.create_networking_config({ net_name: self.client.create_endpoint_config( @@ -257,7 +257,7 @@ class TestNetworks(BaseAPIIntegrationTest): @requires_api_version('1.24') def test_create_with_linklocal_ips(self): container = self.client.create_container( - BUSYBOX, 'top', + TEST_IMG, 'top', networking_config=self.client.create_networking_config( { 'bridge': self.client.create_endpoint_config( diff --git a/tests/integration/api_plugin_test.py b/tests/integration/api_plugin_test.py index 1150b09..38f9d12 100644 --- a/tests/integration/api_plugin_test.py +++ b/tests/integration/api_plugin_test.py @@ -3,7 +3,7 @@ import os import docker import pytest -from .base import BaseAPIIntegrationTest, TEST_API_VERSION +from .base import BaseAPIIntegrationTest from ..helpers import requires_api_version SSHFS = 'vieux/sshfs:latest' @@ -13,27 +13,27 @@ SSHFS = 'vieux/sshfs:latest' class PluginTest(BaseAPIIntegrationTest): @classmethod def teardown_class(cls): - c = docker.APIClient( - version=TEST_API_VERSION, timeout=60, - **docker.utils.kwargs_from_env() - ) + client = cls.get_client_instance() try: - c.remove_plugin(SSHFS, force=True) + client.remove_plugin(SSHFS, force=True) except docker.errors.APIError: pass def teardown_method(self, method): + client = self.get_client_instance() try: - self.client.disable_plugin(SSHFS) + client.disable_plugin(SSHFS) except docker.errors.APIError: pass for p in self.tmp_plugins: try: - self.client.remove_plugin(p, force=True) + client.remove_plugin(p, force=True) except docker.errors.APIError: pass + client.close() + def ensure_plugin_installed(self, plugin_name): try: return self.client.inspect_plugin(plugin_name) diff --git a/tests/integration/api_service_test.py b/tests/integration/api_service_test.py index 85f9dcc..b6b7ec5 100644 --- a/tests/integration/api_service_test.py +++ b/tests/integration/api_service_test.py @@ -10,7 +10,7 @@ import six from ..helpers import ( force_leave_swarm, requires_api_version, requires_experimental ) -from .base import BaseAPIIntegrationTest, BUSYBOX +from .base import BaseAPIIntegrationTest, TEST_IMG class ServiceTest(BaseAPIIntegrationTest): @@ -60,7 +60,7 @@ class ServiceTest(BaseAPIIntegrationTest): name = self.get_service_name() container_spec = docker.types.ContainerSpec( - BUSYBOX, ['echo', 'hello'] + TEST_IMG, ['echo', 'hello'] ) task_tmpl = docker.types.TaskTemplate(container_spec) return name, self.client.create_service( @@ -156,7 +156,7 @@ class ServiceTest(BaseAPIIntegrationTest): def test_create_service_custom_log_driver(self): container_spec = docker.types.ContainerSpec( - BUSYBOX, ['echo', 'hello'] + TEST_IMG, ['echo', 'hello'] ) log_cfg = docker.types.DriverConfig('none') task_tmpl = docker.types.TaskTemplate( @@ -174,7 +174,7 @@ class ServiceTest(BaseAPIIntegrationTest): def test_create_service_with_volume_mount(self): vol_name = self.get_service_name() container_spec = docker.types.ContainerSpec( - BUSYBOX, ['ls'], + TEST_IMG, ['ls'], mounts=[ docker.types.Mount(target='/test', source=vol_name) ] @@ -194,7 +194,7 @@ class ServiceTest(BaseAPIIntegrationTest): assert mount['Type'] == 'volume' def test_create_service_with_resources_constraints(self): - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) resources = docker.types.Resources( cpu_limit=4000000, mem_limit=3 * 1024 * 1024 * 1024, cpu_reservation=3500000, mem_reservation=2 * 1024 * 1024 * 1024 @@ -214,7 +214,7 @@ class ServiceTest(BaseAPIIntegrationTest): ] def _create_service_with_generic_resources(self, generic_resources): - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) resources = docker.types.Resources( generic_resources=generic_resources @@ -265,7 +265,7 @@ class ServiceTest(BaseAPIIntegrationTest): self._create_service_with_generic_resources(test_input) def test_create_service_with_update_config(self): - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) task_tmpl = docker.types.TaskTemplate(container_spec) update_config = docker.types.UpdateConfig( parallelism=10, delay=5, failure_action='pause' @@ -281,6 +281,20 @@ class ServiceTest(BaseAPIIntegrationTest): assert update_config['Delay'] == uc['Delay'] assert update_config['FailureAction'] == uc['FailureAction'] + @requires_api_version('1.28') + def test_create_service_with_failure_action_rollback(self): + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) + task_tmpl = docker.types.TaskTemplate(container_spec) + update_config = docker.types.UpdateConfig(failure_action='rollback') + name = self.get_service_name() + svc_id = self.client.create_service( + task_tmpl, update_config=update_config, name=name + ) + svc_info = self.client.inspect_service(svc_id) + assert 'UpdateConfig' in svc_info['Spec'] + uc = svc_info['Spec']['UpdateConfig'] + assert update_config['FailureAction'] == uc['FailureAction'] + @requires_api_version('1.25') def test_create_service_with_update_config_monitor(self): container_spec = docker.types.ContainerSpec('busybox', ['true']) @@ -298,8 +312,29 @@ class ServiceTest(BaseAPIIntegrationTest): assert update_config['Monitor'] == uc['Monitor'] assert update_config['MaxFailureRatio'] == uc['MaxFailureRatio'] + @requires_api_version('1.28') + def test_create_service_with_rollback_config(self): + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) + task_tmpl = docker.types.TaskTemplate(container_spec) + rollback_cfg = docker.types.RollbackConfig( + parallelism=10, delay=5, failure_action='pause', + monitor=300000000, max_failure_ratio=0.4 + ) + name = self.get_service_name() + svc_id = self.client.create_service( + task_tmpl, rollback_config=rollback_cfg, name=name + ) + svc_info = self.client.inspect_service(svc_id) + assert 'RollbackConfig' in svc_info['Spec'] + rc = svc_info['Spec']['RollbackConfig'] + assert rollback_cfg['Parallelism'] == rc['Parallelism'] + assert rollback_cfg['Delay'] == rc['Delay'] + assert rollback_cfg['FailureAction'] == rc['FailureAction'] + assert rollback_cfg['Monitor'] == rc['Monitor'] + assert rollback_cfg['MaxFailureRatio'] == rc['MaxFailureRatio'] + def test_create_service_with_restart_policy(self): - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) policy = docker.types.RestartPolicy( docker.types.RestartPolicy.condition_types.ANY, delay=5, max_attempts=5 @@ -322,7 +357,7 @@ class ServiceTest(BaseAPIIntegrationTest): 'dockerpytest_2', driver='overlay', ipam={'Driver': 'default'} ) self.tmp_networks.append(net2['Id']) - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) task_tmpl = docker.types.TaskTemplate(container_spec) name = self.get_service_name() svc_id = self.client.create_service( @@ -336,9 +371,38 @@ class ServiceTest(BaseAPIIntegrationTest): {'Target': net1['Id']}, {'Target': net2['Id']} ] + def test_create_service_with_network_attachment_config(self): + network = self.client.create_network( + 'dockerpytest_1', driver='overlay', ipam={'Driver': 'default'} + ) + self.tmp_networks.append(network['Id']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) + network_config = docker.types.NetworkAttachmentConfig( + target='dockerpytest_1', + aliases=['dockerpytest_1_alias'], + options={ + 'foo': 'bar' + } + ) + task_tmpl = docker.types.TaskTemplate( + container_spec, + networks=[network_config] + ) + name = self.get_service_name() + svc_id = self.client.create_service( + task_tmpl, name=name + ) + svc_info = self.client.inspect_service(svc_id) + assert 'Networks' in svc_info['Spec']['TaskTemplate'] + service_networks_info = svc_info['Spec']['TaskTemplate']['Networks'] + assert len(service_networks_info) == 1 + assert service_networks_info[0]['Target'] == network['Id'] + assert service_networks_info[0]['Aliases'] == ['dockerpytest_1_alias'] + assert service_networks_info[0]['DriverOpts'] == {'foo': 'bar'} + def test_create_service_with_placement(self): node_id = self.client.nodes()[0]['ID'] - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) task_tmpl = docker.types.TaskTemplate( container_spec, placement=['node.id=={}'.format(node_id)] ) @@ -351,7 +415,7 @@ class ServiceTest(BaseAPIIntegrationTest): def test_create_service_with_placement_object(self): node_id = self.client.nodes()[0]['ID'] - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) placemt = docker.types.Placement( constraints=['node.id=={}'.format(node_id)] ) @@ -366,7 +430,7 @@ class ServiceTest(BaseAPIIntegrationTest): @requires_api_version('1.30') def test_create_service_with_placement_platform(self): - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) placemt = docker.types.Placement(platforms=[('x86_64', 'linux')]) task_tmpl = docker.types.TaskTemplate( container_spec, placement=placemt @@ -379,7 +443,7 @@ class ServiceTest(BaseAPIIntegrationTest): @requires_api_version('1.27') def test_create_service_with_placement_preferences(self): - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) placemt = docker.types.Placement(preferences=[ {'Spread': {'SpreadDescriptor': 'com.dockerpy.test'}} ]) @@ -392,8 +456,23 @@ class ServiceTest(BaseAPIIntegrationTest): assert 'Placement' in svc_info['Spec']['TaskTemplate'] assert svc_info['Spec']['TaskTemplate']['Placement'] == placemt + @requires_api_version('1.27') + def test_create_service_with_placement_preferences_tuple(self): + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) + placemt = docker.types.Placement(preferences=( + ('spread', 'com.dockerpy.test'), + )) + task_tmpl = docker.types.TaskTemplate( + container_spec, placement=placemt + ) + name = self.get_service_name() + svc_id = self.client.create_service(task_tmpl, name=name) + svc_info = self.client.inspect_service(svc_id) + assert 'Placement' in svc_info['Spec']['TaskTemplate'] + assert svc_info['Spec']['TaskTemplate']['Placement'] == placemt + def test_create_service_with_endpoint_spec(self): - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) task_tmpl = docker.types.TaskTemplate(container_spec) name = self.get_service_name() endpoint_spec = docker.types.EndpointSpec(ports={ @@ -423,7 +502,7 @@ class ServiceTest(BaseAPIIntegrationTest): @requires_api_version('1.32') def test_create_service_with_endpoint_spec_host_publish_mode(self): - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) task_tmpl = docker.types.TaskTemplate(container_spec) name = self.get_service_name() endpoint_spec = docker.types.EndpointSpec(ports={ @@ -443,7 +522,7 @@ class ServiceTest(BaseAPIIntegrationTest): def test_create_service_with_env(self): container_spec = docker.types.ContainerSpec( - BUSYBOX, ['true'], env={'DOCKER_PY_TEST': 1} + TEST_IMG, ['true'], env={'DOCKER_PY_TEST': 1} ) task_tmpl = docker.types.TaskTemplate( container_spec, @@ -459,7 +538,7 @@ class ServiceTest(BaseAPIIntegrationTest): @requires_api_version('1.29') def test_create_service_with_update_order(self): - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) task_tmpl = docker.types.TaskTemplate(container_spec) update_config = docker.types.UpdateConfig( parallelism=10, delay=5, order='start-first' @@ -478,7 +557,7 @@ class ServiceTest(BaseAPIIntegrationTest): @requires_api_version('1.25') def test_create_service_with_tty(self): container_spec = docker.types.ContainerSpec( - BUSYBOX, ['true'], tty=True + TEST_IMG, ['true'], tty=True ) task_tmpl = docker.types.TaskTemplate( container_spec, @@ -495,7 +574,7 @@ class ServiceTest(BaseAPIIntegrationTest): @requires_api_version('1.25') def test_create_service_with_tty_dict(self): container_spec = { - 'Image': BUSYBOX, + 'Image': TEST_IMG, 'Command': ['true'], 'TTY': True } @@ -511,7 +590,7 @@ class ServiceTest(BaseAPIIntegrationTest): def test_create_service_global_mode(self): container_spec = docker.types.ContainerSpec( - BUSYBOX, ['echo', 'hello'] + TEST_IMG, ['echo', 'hello'] ) task_tmpl = docker.types.TaskTemplate(container_spec) name = self.get_service_name() @@ -524,7 +603,7 @@ class ServiceTest(BaseAPIIntegrationTest): def test_create_service_replicated_mode(self): container_spec = docker.types.ContainerSpec( - BUSYBOX, ['echo', 'hello'] + TEST_IMG, ['echo', 'hello'] ) task_tmpl = docker.types.TaskTemplate(container_spec) name = self.get_service_name() @@ -717,7 +796,7 @@ class ServiceTest(BaseAPIIntegrationTest): search=['local'], options=['debug'] ) container_spec = docker.types.ContainerSpec( - BUSYBOX, ['sleep', '999'], dns_config=dns_config + TEST_IMG, ['sleep', '999'], dns_config=dns_config ) task_tmpl = docker.types.TaskTemplate(container_spec) name = self.get_service_name() @@ -737,7 +816,7 @@ class ServiceTest(BaseAPIIntegrationTest): start_period=3 * second, interval=int(second / 2), ) container_spec = docker.types.ContainerSpec( - BUSYBOX, ['sleep', '999'], healthcheck=hc + TEST_IMG, ['sleep', '999'], healthcheck=hc ) task_tmpl = docker.types.TaskTemplate(container_spec) name = self.get_service_name() @@ -754,7 +833,7 @@ class ServiceTest(BaseAPIIntegrationTest): @requires_api_version('1.28') def test_create_service_with_readonly(self): container_spec = docker.types.ContainerSpec( - BUSYBOX, ['sleep', '999'], read_only=True + TEST_IMG, ['sleep', '999'], read_only=True ) task_tmpl = docker.types.TaskTemplate(container_spec) name = self.get_service_name() @@ -768,7 +847,7 @@ class ServiceTest(BaseAPIIntegrationTest): @requires_api_version('1.28') def test_create_service_with_stop_signal(self): container_spec = docker.types.ContainerSpec( - BUSYBOX, ['sleep', '999'], stop_signal='SIGINT' + TEST_IMG, ['sleep', '999'], stop_signal='SIGINT' ) task_tmpl = docker.types.TaskTemplate(container_spec) name = self.get_service_name() @@ -786,7 +865,7 @@ class ServiceTest(BaseAPIIntegrationTest): def test_create_service_with_privileges(self): priv = docker.types.Privileges(selinux_disable=True) container_spec = docker.types.ContainerSpec( - BUSYBOX, ['sleep', '999'], privileges=priv + TEST_IMG, ['sleep', '999'], privileges=priv ) task_tmpl = docker.types.TaskTemplate(container_spec) name = self.get_service_name() @@ -800,6 +879,20 @@ class ServiceTest(BaseAPIIntegrationTest): ) assert privileges['SELinuxContext']['Disable'] is True + @requires_api_version('1.38') + def test_create_service_with_init(self): + container_spec = docker.types.ContainerSpec( + 'busybox', ['sleep', '999'], init=True + ) + task_tmpl = docker.types.TaskTemplate(container_spec) + name = self.get_service_name() + svc_id = self.client.create_service(task_tmpl, name=name) + svc_info = self.client.inspect_service(svc_id) + assert 'Init' in svc_info['Spec']['TaskTemplate']['ContainerSpec'] + assert ( + svc_info['Spec']['TaskTemplate']['ContainerSpec']['Init'] is True + ) + @requires_api_version('1.25') def test_update_service_with_defaults_name(self): container_spec = docker.types.ContainerSpec( @@ -928,7 +1021,7 @@ class ServiceTest(BaseAPIIntegrationTest): assert labels['container.label'] == 'SampleLabel' def test_update_service_with_defaults_update_config(self): - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) task_tmpl = docker.types.TaskTemplate(container_spec) update_config = docker.types.UpdateConfig( parallelism=10, delay=5, failure_action='pause' @@ -967,7 +1060,7 @@ class ServiceTest(BaseAPIIntegrationTest): 'dockerpytest_2', driver='overlay', ipam={'Driver': 'default'} ) self.tmp_networks.append(net2['Id']) - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) task_tmpl = docker.types.TaskTemplate(container_spec) name = self.get_service_name() svc_id = self.client.create_service( @@ -1006,7 +1099,7 @@ class ServiceTest(BaseAPIIntegrationTest): ] def test_update_service_with_defaults_endpoint_spec(self): - container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + container_spec = docker.types.ContainerSpec(TEST_IMG, ['true']) task_tmpl = docker.types.TaskTemplate(container_spec) name = self.get_service_name() endpoint_spec = docker.types.EndpointSpec(ports={ @@ -1070,7 +1163,7 @@ class ServiceTest(BaseAPIIntegrationTest): start_period=3 * second, interval=int(second / 2), ) container_spec = docker.types.ContainerSpec( - BUSYBOX, ['sleep', '999'], healthcheck=hc + TEST_IMG, ['sleep', '999'], healthcheck=hc ) task_tmpl = docker.types.TaskTemplate(container_spec) name = self.get_service_name() @@ -1085,7 +1178,7 @@ class ServiceTest(BaseAPIIntegrationTest): ) container_spec = docker.types.ContainerSpec( - BUSYBOX, ['sleep', '999'], healthcheck={} + TEST_IMG, ['sleep', '999'], healthcheck={} ) task_tmpl = docker.types.TaskTemplate(container_spec) diff --git a/tests/integration/api_swarm_test.py b/tests/integration/api_swarm_test.py index dbf3786..f1cbc26 100644 --- a/tests/integration/api_swarm_test.py +++ b/tests/integration/api_swarm_test.py @@ -13,14 +13,13 @@ class SwarmTest(BaseAPIIntegrationTest): self._unlock_key = None def tearDown(self): - super(SwarmTest, self).tearDown() try: if self._unlock_key: self.client.unlock_swarm(self._unlock_key) except docker.errors.APIError: pass - force_leave_swarm(self.client) + super(SwarmTest, self).tearDown() @requires_api_version('1.24') def test_init_swarm_simple(self): @@ -36,6 +35,35 @@ class SwarmTest(BaseAPIIntegrationTest): version_2 = self.client.inspect_swarm()['Version']['Index'] assert version_2 != version_1 + @requires_api_version('1.39') + def test_init_swarm_custom_addr_pool_defaults(self): + assert self.init_swarm() + results = self.client.inspect_swarm() + assert set(results['DefaultAddrPool']) == {'10.0.0.0/8'} + assert results['SubnetSize'] == 24 + + @requires_api_version('1.39') + def test_init_swarm_custom_addr_pool_only_pool(self): + assert self.init_swarm(default_addr_pool=['2.0.0.0/16']) + results = self.client.inspect_swarm() + assert set(results['DefaultAddrPool']) == {'2.0.0.0/16'} + assert results['SubnetSize'] == 24 + + @requires_api_version('1.39') + def test_init_swarm_custom_addr_pool_only_subnet_size(self): + assert self.init_swarm(subnet_size=26) + results = self.client.inspect_swarm() + assert set(results['DefaultAddrPool']) == {'10.0.0.0/8'} + assert results['SubnetSize'] == 26 + + @requires_api_version('1.39') + def test_init_swarm_custom_addr_pool_both_args(self): + assert self.init_swarm(default_addr_pool=['2.0.0.0/16', '3.0.0.0/16'], + subnet_size=28) + results = self.client.inspect_swarm() + assert set(results['DefaultAddrPool']) == {'2.0.0.0/16', '3.0.0.0/16'} + assert results['SubnetSize'] == 28 + @requires_api_version('1.24') def test_init_already_in_cluster(self): assert self.init_swarm() @@ -158,12 +186,14 @@ class SwarmTest(BaseAPIIntegrationTest): @requires_api_version('1.24') def test_inspect_node(self): - assert self.init_swarm() + node_id = self.init_swarm() + assert node_id nodes_list = self.client.nodes() assert len(nodes_list) == 1 node = nodes_list[0] node_data = self.client.inspect_node(node['ID']) assert node['ID'] == node_data['ID'] + assert node_id == node['ID'] assert node['Version'] == node_data['Version'] @requires_api_version('1.24') @@ -205,3 +235,21 @@ class SwarmTest(BaseAPIIntegrationTest): self.client.remove_node(node_id, True) assert e.value.response.status_code >= 400 + + @requires_api_version('1.25') + def test_rotate_manager_unlock_key(self): + spec = self.client.create_swarm_spec(autolock_managers=True) + assert self.init_swarm(swarm_spec=spec) + swarm_info = self.client.inspect_swarm() + key_1 = self.client.get_unlock_key() + assert self.client.update_swarm( + version=swarm_info['Version']['Index'], + rotate_manager_unlock_key=True + ) + key_2 = self.client.get_unlock_key() + assert key_1['UnlockKey'] != key_2['UnlockKey'] + + @requires_api_version('1.30') + @pytest.mark.xfail(reason='Can fail if eth0 has multiple IP addresses') + def test_init_swarm_data_path_addr(self): + assert self.init_swarm(data_path_addr='eth0') diff --git a/tests/integration/base.py b/tests/integration/base.py index 56c23ed..a7613f6 100644 --- a/tests/integration/base.py +++ b/tests/integration/base.py @@ -3,11 +3,10 @@ import shutil import unittest import docker -from docker.utils import kwargs_from_env - from .. import helpers +from docker.utils import kwargs_from_env -BUSYBOX = 'busybox:buildroot-2014.02' +TEST_IMG = 'alpine:3.10' TEST_API_VERSION = os.environ.get('DOCKER_TEST_API_VERSION') @@ -29,41 +28,44 @@ class BaseIntegrationTest(unittest.TestCase): def tearDown(self): client = docker.from_env(version=TEST_API_VERSION) - for img in self.tmp_imgs: - try: - client.api.remove_image(img) - except docker.errors.APIError: - pass - for container in self.tmp_containers: - try: - client.api.remove_container(container, force=True, v=True) - except docker.errors.APIError: - pass - for network in self.tmp_networks: - try: - client.api.remove_network(network) - except docker.errors.APIError: - pass - for volume in self.tmp_volumes: - try: - client.api.remove_volume(volume) - except docker.errors.APIError: - pass - - for secret in self.tmp_secrets: - try: - client.api.remove_secret(secret) - except docker.errors.APIError: - pass - - for config in self.tmp_configs: - try: - client.api.remove_config(config) - except docker.errors.APIError: - pass - - for folder in self.tmp_folders: - shutil.rmtree(folder) + try: + for img in self.tmp_imgs: + try: + client.api.remove_image(img) + except docker.errors.APIError: + pass + for container in self.tmp_containers: + try: + client.api.remove_container(container, force=True, v=True) + except docker.errors.APIError: + pass + for network in self.tmp_networks: + try: + client.api.remove_network(network) + except docker.errors.APIError: + pass + for volume in self.tmp_volumes: + try: + client.api.remove_volume(volume) + except docker.errors.APIError: + pass + + for secret in self.tmp_secrets: + try: + client.api.remove_secret(secret) + except docker.errors.APIError: + pass + + for config in self.tmp_configs: + try: + client.api.remove_config(config) + except docker.errors.APIError: + pass + + for folder in self.tmp_folders: + shutil.rmtree(folder) + finally: + client.close() class BaseAPIIntegrationTest(BaseIntegrationTest): @@ -106,7 +108,7 @@ class BaseAPIIntegrationTest(BaseIntegrationTest): return container - def create_and_start(self, image=BUSYBOX, command='top', **kwargs): + def create_and_start(self, image=TEST_IMG, command='top', **kwargs): container = self.client.create_container( image=image, command=command, **kwargs) self.tmp_containers.append(container) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 4e8d268..ec48835 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -7,7 +7,7 @@ import docker.errors from docker.utils import kwargs_from_env import pytest -from .base import BUSYBOX +from .base import TEST_IMG @pytest.fixture(autouse=True, scope='session') @@ -15,15 +15,15 @@ def setup_test_session(): warnings.simplefilter('error') c = docker.APIClient(version='auto', **kwargs_from_env()) try: - c.inspect_image(BUSYBOX) + c.inspect_image(TEST_IMG) except docker.errors.NotFound: - print("\npulling {0}".format(BUSYBOX), file=sys.stderr) - for data in c.pull(BUSYBOX, stream=True, decode=True): + print("\npulling {0}".format(TEST_IMG), file=sys.stderr) + for data in c.pull(TEST_IMG, stream=True, decode=True): status = data.get("status") progress = data.get("progress") detail = "{0} - {1}".format(status, progress) print(detail, file=sys.stderr) # Double make sure we now have busybox - c.inspect_image(BUSYBOX) + c.inspect_image(TEST_IMG) c.close() diff --git a/tests/integration/credentials/__init__.py b/tests/integration/credentials/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/integration/credentials/__init__.py diff --git a/tests/integration/credentials/store_test.py b/tests/integration/credentials/store_test.py new file mode 100644 index 0000000..dd543e2 --- /dev/null +++ b/tests/integration/credentials/store_test.py @@ -0,0 +1,87 @@ +import os +import random +import sys + +import pytest +import six +from distutils.spawn import find_executable + +from docker.credentials import ( + CredentialsNotFound, Store, StoreError, DEFAULT_LINUX_STORE, + DEFAULT_OSX_STORE +) + + +class TestStore(object): + def teardown_method(self): + for server in self.tmp_keys: + try: + self.store.erase(server) + except StoreError: + pass + + def setup_method(self): + self.tmp_keys = [] + if sys.platform.startswith('linux'): + if find_executable('docker-credential-' + DEFAULT_LINUX_STORE): + self.store = Store(DEFAULT_LINUX_STORE) + elif find_executable('docker-credential-pass'): + self.store = Store('pass') + else: + raise Exception('No supported docker-credential store in PATH') + elif sys.platform.startswith('darwin'): + self.store = Store(DEFAULT_OSX_STORE) + + def get_random_servername(self): + res = 'pycreds_test_{:x}'.format(random.getrandbits(32)) + self.tmp_keys.append(res) + return res + + def test_store_and_get(self): + key = self.get_random_servername() + self.store.store(server=key, username='user', secret='pass') + data = self.store.get(key) + assert data == { + 'ServerURL': key, + 'Username': 'user', + 'Secret': 'pass' + } + + def test_get_nonexistent(self): + key = self.get_random_servername() + with pytest.raises(CredentialsNotFound): + self.store.get(key) + + def test_store_and_erase(self): + key = self.get_random_servername() + self.store.store(server=key, username='user', secret='pass') + self.store.erase(key) + with pytest.raises(CredentialsNotFound): + self.store.get(key) + + def test_unicode_strings(self): + key = self.get_random_servername() + key = six.u(key) + self.store.store(server=key, username='user', secret='pass') + data = self.store.get(key) + assert data + self.store.erase(key) + with pytest.raises(CredentialsNotFound): + self.store.get(key) + + def test_list(self): + names = (self.get_random_servername(), self.get_random_servername()) + self.store.store(names[0], username='sakuya', secret='izayoi') + self.store.store(names[1], username='reimu', secret='hakurei') + data = self.store.list() + assert names[0] in data + assert data[names[0]] == 'sakuya' + assert names[1] in data + assert data[names[1]] == 'reimu' + + def test_execute_with_env_override(self): + self.store.exe = 'env' + self.store.environment = {'FOO': 'bar'} + data = self.store._execute('--null', '') + assert b'\0FOO=bar\0' in data + assert 'FOO' not in os.environ diff --git a/tests/integration/credentials/utils_test.py b/tests/integration/credentials/utils_test.py new file mode 100644 index 0000000..ad55f32 --- /dev/null +++ b/tests/integration/credentials/utils_test.py @@ -0,0 +1,22 @@ +import os + +from docker.credentials.utils import create_environment_dict + +try: + from unittest import mock +except ImportError: + import mock + + +@mock.patch.dict(os.environ) +def test_create_environment_dict(): + base = {'FOO': 'bar', 'BAZ': 'foobar'} + os.environ = base + assert create_environment_dict({'FOO': 'baz'}) == { + 'FOO': 'baz', 'BAZ': 'foobar', + } + assert create_environment_dict({'HELLO': 'world'}) == { + 'FOO': 'bar', 'BAZ': 'foobar', 'HELLO': 'world', + } + + assert os.environ == base diff --git a/tests/integration/errors_test.py b/tests/integration/errors_test.py index ac74d72..7bf156a 100644 --- a/tests/integration/errors_test.py +++ b/tests/integration/errors_test.py @@ -1,11 +1,11 @@ from docker.errors import APIError -from .base import BaseAPIIntegrationTest, BUSYBOX +from .base import BaseAPIIntegrationTest, TEST_IMG import pytest class ErrorsTest(BaseAPIIntegrationTest): def test_api_error_parses_json(self): - container = self.client.create_container(BUSYBOX, ['sleep', '10']) + container = self.client.create_container(TEST_IMG, ['sleep', '10']) self.client.start(container['Id']) with pytest.raises(APIError) as cm: self.client.remove_container(container['Id']) diff --git a/tests/integration/models_containers_test.py b/tests/integration/models_containers_test.py index ab41ea5..eac4c97 100644 --- a/tests/integration/models_containers_test.py +++ b/tests/integration/models_containers_test.py @@ -1,10 +1,14 @@ +import os import tempfile import threading -import docker import pytest -from .base import BaseIntegrationTest, TEST_API_VERSION -from ..helpers import random_name, requires_api_version + +import docker +from ..helpers import random_name +from ..helpers import requires_api_version +from .base import BaseIntegrationTest +from .base import TEST_API_VERSION class ContainerCollectionTest(BaseIntegrationTest): @@ -122,7 +126,9 @@ class ContainerCollectionTest(BaseIntegrationTest): def test_run_with_auto_remove(self): client = docker.from_env(version=TEST_API_VERSION) out = client.containers.run( - 'alpine', 'echo hello', auto_remove=True + # sleep(2) to allow any communication with the container + # before it gets removed by the host. + 'alpine', 'sh -c "echo hello && sleep 2"', auto_remove=True ) assert out == b'hello\n' @@ -131,7 +137,10 @@ class ContainerCollectionTest(BaseIntegrationTest): client = docker.from_env(version=TEST_API_VERSION) with pytest.raises(docker.errors.ContainerError) as e: client.containers.run( - 'alpine', 'sh -c ">&2 echo error && exit 1"', auto_remove=True + # sleep(2) to allow any communication with the container + # before it gets removed by the host. + 'alpine', 'sh -c ">&2 echo error && sleep 2 && exit 1"', + auto_remove=True ) assert e.value.exit_status == 1 assert e.value.stderr is None @@ -146,6 +155,8 @@ class ContainerCollectionTest(BaseIntegrationTest): assert logs[1] == b'world\n' @pytest.mark.timeout(5) + @pytest.mark.skipif(os.environ.get('DOCKER_HOST', '').startswith('ssh://'), + reason='No cancellable streams over SSH') def test_run_with_streamed_logs_and_cancel(self): client = docker.from_env(version=TEST_API_VERSION) out = client.containers.run( @@ -160,6 +171,17 @@ class ContainerCollectionTest(BaseIntegrationTest): assert logs[0] == b'hello\n' assert logs[1] == b'world\n' + def test_run_with_proxy_config(self): + client = docker.from_env(version=TEST_API_VERSION) + client.api._proxy_configs = docker.utils.proxy.ProxyConfig( + ftp='sakuya.jp:4967' + ) + + out = client.containers.run('alpine', 'sh -c "env"') + + assert b'FTP_PROXY=sakuya.jp:4967\n' in out + assert b'ftp_proxy=sakuya.jp:4967\n' in out + def test_get(self): client = docker.from_env(version=TEST_API_VERSION) container = client.containers.run("alpine", "sleep 300", detach=True) @@ -325,6 +347,66 @@ class ContainerTest(BaseIntegrationTest): 'memory_stats', 'blkio_stats']: assert key in stats + def test_ports_target_none(self): + client = docker.from_env(version=TEST_API_VERSION) + ports = None + target_ports = {'2222/tcp': ports} + container = client.containers.run( + "alpine", "sleep 100", detach=True, + ports=target_ports + ) + self.tmp_containers.append(container.id) + container.reload() # required to get auto-assigned ports + actual_ports = container.ports + assert sorted(target_ports.keys()) == sorted(actual_ports.keys()) + for target_client, target_host in target_ports.items(): + for actual_port in actual_ports[target_client]: + actual_keys = sorted(actual_port.keys()) + assert sorted(['HostIp', 'HostPort']) == actual_keys + assert target_host is ports + assert int(actual_port['HostPort']) > 0 + client.close() + + def test_ports_target_tuple(self): + client = docker.from_env(version=TEST_API_VERSION) + ports = ('127.0.0.1', 1111) + target_ports = {'2222/tcp': ports} + container = client.containers.run( + "alpine", "sleep 100", detach=True, + ports=target_ports + ) + self.tmp_containers.append(container.id) + container.reload() # required to get auto-assigned ports + actual_ports = container.ports + assert sorted(target_ports.keys()) == sorted(actual_ports.keys()) + for target_client, target_host in target_ports.items(): + for actual_port in actual_ports[target_client]: + actual_keys = sorted(actual_port.keys()) + assert sorted(['HostIp', 'HostPort']) == actual_keys + assert target_host == ports + assert int(actual_port['HostPort']) > 0 + client.close() + + def test_ports_target_list(self): + client = docker.from_env(version=TEST_API_VERSION) + ports = [1234, 4567] + target_ports = {'2222/tcp': ports} + container = client.containers.run( + "alpine", "sleep 100", detach=True, + ports=target_ports + ) + self.tmp_containers.append(container.id) + container.reload() # required to get auto-assigned ports + actual_ports = container.ports + assert sorted(target_ports.keys()) == sorted(actual_ports.keys()) + for target_client, target_host in target_ports.items(): + for actual_port in actual_ports[target_client]: + actual_keys = sorted(actual_port.keys()) + assert sorted(['HostIp', 'HostPort']) == actual_keys + assert target_host == ports + assert int(actual_port['HostPort']) > 0 + client.close() + def test_stop(self): client = docker.from_env(version=TEST_API_VERSION) container = client.containers.run("alpine", "top", detach=True) @@ -362,3 +444,13 @@ class ContainerTest(BaseIntegrationTest): detach=True) self.tmp_containers.append(container.id) assert container.wait()['StatusCode'] == 1 + + def test_create_with_volume_driver(self): + client = docker.from_env(version=TEST_API_VERSION) + container = client.containers.create( + 'alpine', + 'sleep 300', + volume_driver='foo' + ) + self.tmp_containers.append(container.id) + assert container.attrs['HostConfig']['VolumeDriver'] == 'foo' diff --git a/tests/integration/models_images_test.py b/tests/integration/models_images_test.py index ae735ba..375d972 100644 --- a/tests/integration/models_images_test.py +++ b/tests/integration/models_images_test.py @@ -4,7 +4,8 @@ import tempfile import docker import pytest -from .base import BaseIntegrationTest, BUSYBOX, TEST_API_VERSION +from .base import BaseIntegrationTest, TEST_IMG, TEST_API_VERSION +from ..helpers import random_name class ImageCollectionTest(BaseIntegrationTest): @@ -71,8 +72,8 @@ class ImageCollectionTest(BaseIntegrationTest): def test_pull_with_tag(self): client = docker.from_env(version=TEST_API_VERSION) - image = client.images.pull('alpine', tag='3.3') - assert 'alpine:3.3' in image.attrs['RepoTags'] + image = client.images.pull('alpine', tag='3.10') + assert 'alpine:3.10' in image.attrs['RepoTags'] def test_pull_with_sha(self): image_ref = ( @@ -96,7 +97,7 @@ class ImageCollectionTest(BaseIntegrationTest): def test_save_and_load(self): client = docker.from_env(version=TEST_API_VERSION) - image = client.images.get(BUSYBOX) + image = client.images.get(TEST_IMG) with tempfile.TemporaryFile() as f: stream = image.save() for chunk in stream: @@ -108,6 +109,32 @@ class ImageCollectionTest(BaseIntegrationTest): assert len(result) == 1 assert result[0].id == image.id + def test_save_and_load_repo_name(self): + client = docker.from_env(version=TEST_API_VERSION) + image = client.images.get(TEST_IMG) + additional_tag = random_name() + image.tag(additional_tag) + self.tmp_imgs.append(additional_tag) + image.reload() + with tempfile.TemporaryFile() as f: + stream = image.save(named='{}:latest'.format(additional_tag)) + for chunk in stream: + f.write(chunk) + + f.seek(0) + client.images.remove(additional_tag, force=True) + result = client.images.load(f.read()) + + assert len(result) == 1 + assert result[0].id == image.id + assert '{}:latest'.format(additional_tag) in result[0].tags + + def test_save_name_error(self): + client = docker.from_env(version=TEST_API_VERSION) + image = client.images.get(TEST_IMG) + with pytest.raises(docker.errors.InvalidArgument): + image.save(named='sakuya/izayoi') + class ImageTest(BaseIntegrationTest): diff --git a/tests/integration/models_swarm_test.py b/tests/integration/models_swarm_test.py index f39f0d3..6c1836d 100644 --- a/tests/integration/models_swarm_test.py +++ b/tests/integration/models_swarm_test.py @@ -31,3 +31,15 @@ class SwarmTest(unittest.TestCase): cm.value.response.status_code == 406 or cm.value.response.status_code == 503 ) + + def test_join_on_already_joined_swarm(self): + client = docker.from_env(version=TEST_API_VERSION) + client.swarm.init() + join_token = client.swarm.attrs['JoinTokens']['Manager'] + with pytest.raises(docker.errors.APIError) as cm: + client.swarm.join( + remote_addrs=['127.0.0.1'], + join_token=join_token, + ) + assert cm.value.response.status_code == 503 + assert 'This node is already part of a swarm.' in cm.value.explanation diff --git a/tests/integration/regression_test.py b/tests/integration/regression_test.py index 0fd4e43..a63883c 100644 --- a/tests/integration/regression_test.py +++ b/tests/integration/regression_test.py @@ -4,7 +4,7 @@ import random import docker import six -from .base import BaseAPIIntegrationTest, BUSYBOX +from .base import BaseAPIIntegrationTest, TEST_IMG import pytest @@ -14,12 +14,12 @@ class TestRegressions(BaseAPIIntegrationTest): with pytest.raises(docker.errors.APIError) as exc: for line in self.client.build(fileobj=dfile, tag="a/b/c"): pass - assert exc.value.response.status_code == 500 + assert exc.value.is_error() dfile.close() def test_542_truncate_ids_client_side(self): self.client.start( - self.client.create_container(BUSYBOX, ['true']) + self.client.create_container(TEST_IMG, ['true']) ) result = self.client.containers(all=True, trunc=True) assert len(result[0]['Id']) == 12 @@ -30,12 +30,12 @@ class TestRegressions(BaseAPIIntegrationTest): def test_649_handle_timeout_value_none(self): self.client.timeout = None - ctnr = self.client.create_container(BUSYBOX, ['sleep', '2']) + ctnr = self.client.create_container(TEST_IMG, ['sleep', '2']) self.client.start(ctnr) self.client.stop(ctnr) def test_715_handle_user_param_as_int_value(self): - ctnr = self.client.create_container(BUSYBOX, ['id', '-u'], user=1000) + ctnr = self.client.create_container(TEST_IMG, ['id', '-u'], user=1000) self.client.start(ctnr) self.client.wait(ctnr) logs = self.client.logs(ctnr) @@ -47,7 +47,7 @@ class TestRegressions(BaseAPIIntegrationTest): tcp_port, udp_port = random.sample(range(9999, 32000), 2) ctnr = self.client.create_container( - BUSYBOX, ['sleep', '9999'], ports=[2000, (2000, 'udp')], + TEST_IMG, ['sleep', '9999'], ports=[2000, (2000, 'udp')], host_config=self.client.create_host_config( port_bindings={'2000/tcp': tcp_port, '2000/udp': udp_port} ) |