diff --git a/docker/client.py b/docker/client.py index 590d1766..0f77dd36 100644 --- a/docker/client.py +++ b/docker/client.py @@ -577,8 +577,9 @@ class Client(requests.Session): if links: formatted_links = [ - '{0}:{1}'.format(path, alias) for path, alias in links.items() + '{0}:{1}'.format(k, v) for k, v in six.iteritems(links) ] + start_config['Links'] = formatted_links url = self._url("/containers/{0}/start".format(container)) diff --git a/tests/fake_api.py b/tests/fake_api.py index 6d115663..3d5a1605 100644 --- a/tests/fake_api.py +++ b/tests/fake_api.py @@ -16,7 +16,13 @@ CURRENT_VERSION = 'v1.6' FAKE_CONTAINER_ID = '3cc2351ab11b' FAKE_IMAGE_ID = 'e9aa60c60128' - +FAKE_IMAGE_NAME = 'test_image' +FAKE_TARBALL_PATH = '/path/to/tarball' +FAKE_REPO_NAME = 'repo' +FAKE_TAG_NAME = 'tag' +FAKE_FILE_NAME = 'file' +FAKE_URL = 'myurl' +FAKE_PATH = '/path' ### Each method is prefixed with HTTP method (get, post...) ### for clarity and readability @@ -51,6 +57,31 @@ def get_fake_images(): return status_code, response +def get_fake_image_history(): + status_code = 200 + response = [ + { + "Id": "b750fe79269d", + "Created": 1364102658, + "CreatedBy": "/bin/bash" + }, + { + "Id": "27cf78414709", + "Created": 1364068391, + "CreatedBy": "" + } + ] + + return status_code, response + + +def post_fake_import_image(): + status_code = 200 + response = 'Import messages...' + + return status_code, response + + def get_fake_containers(): status_code = 200 response = [{ @@ -93,6 +124,45 @@ def get_fake_inspect_container(): return status_code, response +def get_fake_inspect_image(): + status_code = 200 + response = { + 'id': FAKE_IMAGE_ID, + 'parent': "27cf784147099545", + 'created': "2013-03-23T22:24:18.818426-07:00", + 'container': FAKE_CONTAINER_ID, + 'container_config': + { + "Hostname": "", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "AttachStdin": False, + "AttachStdout": False, + "AttachStderr": False, + "PortSpecs": "", + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "Env": "", + "Cmd": ["/bin/bash"], + "Dns": "", + "Image": "base", + "Volumes": "", + "VolumesFrom": "", + "WorkingDir": "" + }, + 'Size': 6823592 + } + return status_code, response + + +def get_fake_insert_image(): + status_code = 200 + response = {'StatusCode': 0} + return status_code, response + + def get_fake_wait(): status_code = 200 response = {'StatusCode': 0} @@ -111,6 +181,12 @@ def get_fake_diff(): return status_code, response +def get_fake_export(): + status_code = 200 + response = 'Byte Stream....' + return status_code, response + + def post_fake_stop_container(): status_code = 200 response = {'Id': FAKE_CONTAINER_ID} @@ -153,12 +229,24 @@ def post_fake_commit(): return status_code, response +def post_fake_push(): + status_code = 200 + response = {'Id': FAKE_IMAGE_ID} + return status_code, response + + def post_fake_build_container(): status_code = 200 response = {'Id': FAKE_CONTAINER_ID} return status_code, response +def post_fake_tag_image(): + status_code = 200 + response = {'Id': FAKE_IMAGE_ID} + return status_code, response + + ## maps real api url to fake response callback prefix = 'unix://var/run/docker.sock' fake_responses = { @@ -170,18 +258,26 @@ fake_responses = { get_fake_search, '{1}/{0}/images/json'.format(CURRENT_VERSION, prefix): get_fake_images, + '{1}/{0}/images/test_image/history'.format(CURRENT_VERSION, prefix): + get_fake_image_history, + '{1}/{0}/images/create'.format(CURRENT_VERSION, prefix): + post_fake_import_image, '{1}/{0}/containers/json'.format(CURRENT_VERSION, prefix): get_fake_containers, '{1}/{0}/containers/3cc2351ab11b/start'.format(CURRENT_VERSION, prefix): post_fake_start_container, '{1}/{0}/containers/3cc2351ab11b/json'.format(CURRENT_VERSION, prefix): get_fake_inspect_container, + '{1}/{0}/images/e9aa60c60128/tag'.format(CURRENT_VERSION, prefix): + post_fake_tag_image, '{1}/{0}/containers/3cc2351ab11b/wait'.format(CURRENT_VERSION, prefix): get_fake_wait, '{1}/{0}/containers/3cc2351ab11b/attach'.format(CURRENT_VERSION, prefix): get_fake_logs, '{1}/{0}/containers/3cc2351ab11b/changes'.format(CURRENT_VERSION, prefix): get_fake_diff, + '{1}/{0}/containers/3cc2351ab11b/export'.format(CURRENT_VERSION, prefix): + get_fake_export, '{1}/{0}/containers/3cc2351ab11b/stop'.format(CURRENT_VERSION, prefix): post_fake_stop_container, '{1}/{0}/containers/3cc2351ab11b/kill'.format(CURRENT_VERSION, prefix): @@ -194,6 +290,12 @@ fake_responses = { post_fake_image_create, '{1}/{0}/images/e9aa60c60128'.format(CURRENT_VERSION, prefix): delete_fake_remove_image, + '{1}/{0}/images/test_image/json'.format(CURRENT_VERSION, prefix): + get_fake_inspect_image, + '{1}/{0}/images/test_image/insert'.format(CURRENT_VERSION, prefix): + get_fake_insert_image, + '{1}/{0}/images/test_image/push'.format(CURRENT_VERSION, prefix): + post_fake_push, '{1}/{0}/commit'.format(CURRENT_VERSION, prefix): post_fake_commit, '{1}/{0}/containers/create'.format(CURRENT_VERSION, prefix): diff --git a/tests/integration_test.py b/tests/integration_test.py index 6480f8c3..6a1082ab 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -31,7 +31,7 @@ class BaseTestCase(unittest.TestCase): tmp_containers = [] def setUp(self): - self.client = docker.Client(version="1.6") + self.client = docker.Client() self.client.pull('busybox') self.tmp_imgs = [] self.tmp_containers = [] diff --git a/tests/test.py b/tests/test.py index 3414be94..469d2bce 100644 --- a/tests/test.py +++ b/tests/test.py @@ -35,7 +35,7 @@ except ImportError: # FIXME: missing tests for -# export; history; import_image; insert; port; push; tag +# port; def response(status_code=200, content='', headers=None, reason=None, elapsed=0, @@ -66,6 +66,8 @@ fake_request = mock.Mock(side_effect=fake_resp) class DockerClientTest(unittest.TestCase): def setUp(self): self.client = docker.Client() + # Force-clear authconfig to avoid tampering with the tests + self.client._cfg = {'Configs': {}} ######################### ## INFORMATION TESTS ## @@ -113,6 +115,16 @@ class DockerClientTest(unittest.TestCase): params={'filter': None, 'only_ids': 0, 'all': 1} ) + def test_images_quiet(self): + try: + self.client.images(all=True, quiet=True) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/images/json', + params={'filter': None, 'only_ids': 1, 'all': 1} + ) + def test_image_ids(self): try: self.client.images(quiet=True) @@ -291,6 +303,61 @@ class DockerClientTest(unittest.TestCase): self.assertEqual(args[1]['headers'], {'Content-Type': 'application/json'}) + def test_start_container_with_links(self): + # one link + try: + link_path = 'path' + alias = 'alias' + self.client.start(fake_api.FAKE_CONTAINER_ID, + links={link_path: alias}) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + args = fake_request.call_args + self.assertEqual( + args[0][0], + 'unix://var/run/docker.sock/v1.6/containers/3cc2351ab11b/start' + ) + self.assertEqual( + json.loads(args[0][1]), + {"PublishAllPorts": False, "Links": ["path:alias"]} + ) + self.assertEqual( + args[1]['headers'], + {'Content-Type': 'application/json'} + ) + + # multiple links + try: + link_path = 'path' + alias = 'alias' + self.client.start( + fake_api.FAKE_CONTAINER_ID, + links={ + link_path + '1': alias + '1', + link_path + '2': alias + '2' + } + ) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + args = fake_request.call_args + self.assertEqual( + args[0][0], + 'unix://var/run/docker.sock/v1.6/containers/3cc2351ab11b/start' + ) + self.assertEqual( + json.loads(args[0][1]), + { + "PublishAllPorts": False, + "Links": ["path2:alias2", "path1:alias1"] + } + ) + self.assertEqual( + args[1]['headers'], + {'Content-Type': 'application/json'} + ) + def test_start_container_with_dict_instead_of_id(self): try: self.client.start({'Id': fake_api.FAKE_CONTAINER_ID}) @@ -302,30 +369,6 @@ class DockerClientTest(unittest.TestCase): headers={'Content-Type': 'application/json'} ) - def test_start_container_with_links(self): - try: - self.client.start( - fake_api.FAKE_CONTAINER_ID, - links={'link1': 'alias1', 'link2': 'alias2'} - ) - except Exception as e: - self.fail('Command should not raise exception: {0}'.format(e)) - - args = fake_request.call_args - self.assertEqual(args[0][0], 'unix://var/run/docker.sock/v1.6/' - 'containers/3cc2351ab11b/start') - params = json.loads(args[0][1]) - self.assertEqual(sorted(["Links", "PublishAllPorts"]), - sorted(params.keys())) - self.assertEqual(params["PublishAllPorts"], False) - # We have to do this since the order is not guaranteed - links = params["Links"] - self.assertTrue(len(["link1:alias1", "link2:alias2"]) == len(links)) - self.assertTrue( - sorted(["link1:alias1", "link2:alias2"]) == sorted(links)) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - def test_wait(self): try: self.client.wait(fake_api.FAKE_CONTAINER_ID) @@ -509,6 +552,38 @@ class DockerClientTest(unittest.TestCase): params={'v': False, 'link': True} ) + def test_export(self): + try: + self.client.export(fake_api.FAKE_CONTAINER_ID) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/containers/3cc2351ab11b/export', + stream=True + ) + + def test_export_with_dict_instead_of_id(self): + try: + self.client.export({'Id': fake_api.FAKE_CONTAINER_ID}) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/containers/3cc2351ab11b/export', + stream=True + ) + + def test_inspect_container(self): + try: + self.client.inspect_container(fake_api.FAKE_CONTAINER_ID) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/containers/3cc2351ab11b/json' + ) + ################## ## IMAGES TESTS ## ################## @@ -576,6 +651,165 @@ class DockerClientTest(unittest.TestCase): 'unix://var/run/docker.sock/v1.6/images/e9aa60c60128' ) + def test_image_history(self): + try: + self.client.history(fake_api.FAKE_IMAGE_NAME) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/images/test_image/history' + ) + + def test_import_image(self): + try: + self.client.import_image( + fake_api.FAKE_TARBALL_PATH, + repository=fake_api.FAKE_REPO_NAME, + tag=fake_api.FAKE_TAG_NAME + ) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/images/create', + None, + params={ + 'repo': fake_api.FAKE_REPO_NAME, + 'tag': fake_api.FAKE_TAG_NAME, + 'fromSrc': fake_api.FAKE_TARBALL_PATH + } + ) + + def test_import_image_from_file(self): + buf = tempfile.NamedTemporaryFile(delete=False) + try: + # pretent the buffer is a file + self.client.import_image( + buf.name, + repository=fake_api.FAKE_REPO_NAME, + tag=fake_api.FAKE_TAG_NAME + ) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/images/create', + '', + params={ + 'repo': fake_api.FAKE_REPO_NAME, + 'tag': fake_api.FAKE_TAG_NAME, + 'fromSrc': '-' + } + ) + buf.close() + os.remove(buf.name) + + def test_inspect_image(self): + try: + self.client.inspect_image(fake_api.FAKE_IMAGE_NAME) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/images/test_image/json' + ) + + def test_insert_image(self): + try: + self.client.insert(fake_api.FAKE_IMAGE_NAME, + fake_api.FAKE_URL, fake_api.FAKE_PATH) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/images/test_image/insert', + None, + params={ + 'url': fake_api.FAKE_URL, + 'path': fake_api.FAKE_PATH + } + ) + + def test_push_image(self): + try: + self.client.push(fake_api.FAKE_IMAGE_NAME) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/images/test_image/push', + '{}', + headers={'Content-Type': 'application/json'}, + stream=False + ) + + def test_push_image_stream(self): + try: + self.client.push(fake_api.FAKE_IMAGE_NAME, stream=True) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/images/test_image/push', + '{}', + headers={'Content-Type': 'application/json'}, + stream=True + ) + + def test_tag_image(self): + try: + self.client.tag(fake_api.FAKE_IMAGE_ID, fake_api.FAKE_REPO_NAME) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/images/e9aa60c60128/tag', + None, + params={ + 'tag': None, + 'repo': 'repo', + 'force': 0 + } + ) + + def test_tag_image_tag(self): + try: + self.client.tag( + fake_api.FAKE_IMAGE_ID, + fake_api.FAKE_REPO_NAME, + tag=fake_api.FAKE_TAG_NAME + ) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/images/e9aa60c60128/tag', + None, + params={ + 'tag': 'tag', + 'repo': 'repo', + 'force': 0 + } + ) + + def test_tag_image_force(self): + try: + self.client.tag( + fake_api.FAKE_IMAGE_ID, fake_api.FAKE_REPO_NAME, force=True) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + + fake_request.assert_called_with( + 'unix://var/run/docker.sock/v1.6/images/e9aa60c60128/tag', + None, + params={ + 'tag': None, + 'repo': 'repo', + 'force': 1 + } + ) + ################# # BUILDER TESTS # #################