diff --git a/README.md b/README.md index 37883156..4b1dfbb2 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,11 @@ Identical to the `docker info` command. * `c.insert(url, path)` Identical to the `docker insert` command. -* `c.inspect_container(container_id)` -Identical to the `docker inspect` command, but can only be used with a -container ID. +* `c.inspect_container(container)` +Identical to the `docker inspect` command, but only for containers. -* `c.inspect_image(container_id)` -Identical to the `docker inspect` command, but can only be used with an -image ID. +* `c.inspect_image(image_id)` +Identical to the `docker inspect` command, but only for images. * `c.kill(container)` Kill a container. Similar to the `docker kill` command. diff --git a/docker/client.py b/docker/client.py index 8d1e4e95..c423a4dc 100644 --- a/docker/client.py +++ b/docker/client.py @@ -94,6 +94,18 @@ class Client(requests.Session): command = shlex.split(str(command)) if isinstance(environment, dict): environment = ['{0}={1}'.format(k, v) for k, v in environment.items()] + + attach_stdin = False + attach_stdout = False + attach_stderr = False + + if not detach: + attach_stdout = True + attach_stderr = True + + if stdin_open: + attach_stdin = True + return { 'Hostname': hostname, 'PortSpecs': ports, @@ -101,9 +113,9 @@ class Client(requests.Session): 'Tty': tty, 'OpenStdin': stdin_open, 'Memory': mem_limit, - 'AttachStdin': False, - 'AttachStdout': False, - 'AttachStderr': False, + 'AttachStdin': attach_stdin, + 'AttachStdout': attach_stdout, + 'AttachStderr': attach_stderr, 'Env': environment, 'Cmd': command, 'Dns': dns, @@ -127,19 +139,26 @@ class Client(requests.Session): kwargs['headers']['Content-Type'] = 'application/json' return self.post(url, json.dumps(data2), **kwargs) - def attach(self, container): - params = { - 'stdout': 1, - 'stderr': 1, - 'stream': 1 - } + def attach_socket(self, container, params=None): + if params is None: + params = { + 'stdout': 1, + 'stderr': 1, + 'stream': 1 + } + if isinstance(container, dict): + container = container.get('Id') + u = self._url("/containers/{0}/attach".format(container)) res = self.post(u, None, params=params, stream=True) self._raise_for_status(res) # hijack the underlying socket from requests, icky # but for some reason requests.iter_contents and ilk # eventually block - socket = res.raw._fp.fp._sock + return res.raw._fp.fp._sock + + def attach(self, container): + socket = self.attach_socket(container) while True: chunk = socket.recv(4096) @@ -225,10 +244,14 @@ class Client(requests.Session): return self._result(res, True) def diff(self, container): + if isinstance(container, dict): + container = container.get('Id') return self._result(self.get(self._url("/containers/{0}/changes". format(container))), True) def export(self, container): + if isinstance(container, dict): + container = container.get('Id') res = self.get(self._url("/containers/{0}/export".format(container)), stream=True) self._raise_for_status(res) @@ -276,15 +299,19 @@ class Client(requests.Session): } return self._result(self.post(api_url, None, params=params)) - def inspect_container(self, container_id): + def inspect_container(self, container): + if isinstance(container, dict): + container = container.get('Id') return self._result(self.get(self._url("/containers/{0}/json". - format(container_id))), True) + format(container))), True) def inspect_image(self, image_id): return self._result(self.get(self._url("/images/{0}/json". format(image_id))), True) def kill(self, container): + if isinstance(container, dict): + container = container.get('Id') url = self._url("/containers/{0}/kill".format(container)) res = self.post(url, None) self._raise_for_status(res) @@ -307,6 +334,8 @@ class Client(requests.Session): return res def logs(self, container): + if isinstance(container, dict): + container = container.get('Id') params = { 'logs': 1, 'stdout': 1, @@ -316,6 +345,8 @@ class Client(requests.Session): return self._result(self.post(u, None, params=params)) def port(self, container, private_port): + if isinstance(container, dict): + container = container.get('Id') res = self.get(self._url("/containers/{0}/json".format(container))) self._raise_for_status(res) json_ = res.json() @@ -360,6 +391,8 @@ class Client(requests.Session): return self._result(self._post_json(u, authcfg)) def remove_container(self, container, v=False): + if isinstance(container, dict): + container = container.get('Id') params = { 'v': v } res = self.delete(self._url("/containers/" + container), params=params) self._raise_for_status(res) @@ -369,6 +402,8 @@ class Client(requests.Session): self._raise_for_status(res) def restart(self, container, timeout=10): + if isinstance(container, dict): + container = container.get('Id') params = { 't': timeout } url = self._url("/containers/{0}/restart".format(container)) res = self.post(url, None, params=params) @@ -379,18 +414,21 @@ class Client(requests.Session): params={'term': term}), True) def start(self, container, binds=None): + if isinstance(container, dict): + container = container.get('Id') start_config = {} if binds: bind_pairs = ['{0}:{1}'.format(host, dest) for host, dest in binds.items()] start_config = { 'Binds': bind_pairs, } - url = self._url("/containers/{0}/start".format(container)) res = self._post_json(url, start_config) self._raise_for_status(res) def stop(self, container, timeout=10): + if isinstance(container, dict): + container = container.get('Id') params = { 't': timeout } url = self._url("/containers/{0}/stop".format(container)) res = self.post(url, None, params=params) @@ -408,13 +446,15 @@ class Client(requests.Session): return res.status_code == 201 def top(self, container): - u = self._url("/containers/{0}/top".format(container) - return self._result(self.get(u)), True) + u = self._url("/containers/{0}/top".format(container)) + return self._result(self.get(u), True) def version(self): return self._result(self.get(self._url("/version")), True) def wait(self, container): + if isinstance(container, dict): + container = container.get('Id') url = self._url("/containers/{0}/wait".format(container)) res = self.post(url, None, timeout=None) self._raise_for_status(res) diff --git a/tests/test.py b/tests/test.py index 8d2ed81a..295a2500 100644 --- a/tests/test.py +++ b/tests/test.py @@ -173,6 +173,23 @@ class TestStartContainer(BaseTestCase): self.assertIn('ExitCode', inspect['State']) self.assertEqual(inspect['State']['ExitCode'], 0) +class TestStartContainerWithDictInsteadOfId(BaseTestCase): + def runTest(self): + res = self.client.create_container('busybox', 'true') + self.assertIn('Id', res) + self.tmp_containers.append(res['Id']) + self.client.start(res) + inspect = self.client.inspect_container(res['Id']) + self.assertIn('Config', inspect) + self.assertIn('ID', inspect) + self.assertTrue(inspect['ID'].startswith(res['Id'])) + self.assertIn('Image', inspect) + self.assertIn('State', inspect) + self.assertIn('Running', inspect['State']) + if not inspect['State']['Running']: + self.assertIn('ExitCode', inspect['State']) + self.assertEqual(inspect['State']['ExitCode'], 0) + class TestWait(BaseTestCase): def runTest(self): res = self.client.create_container('busybox', ['sleep', '10']) @@ -187,6 +204,20 @@ class TestWait(BaseTestCase): self.assertIn('ExitCode', inspect['State']) self.assertEqual(inspect['State']['ExitCode'], exitcode) +class TestWaitWithDictInsteadOfId(BaseTestCase): + def runTest(self): + res = self.client.create_container('busybox', ['sleep', '10']) + id = res['Id'] + self.tmp_containers.append(id) + self.client.start(res) + exitcode = self.client.wait(res) + self.assertEqual(exitcode, 0) + inspect = self.client.inspect_container(res) + self.assertIn('Running', inspect['State']) + self.assertEqual(inspect['State']['Running'], False) + self.assertIn('ExitCode', inspect['State']) + self.assertEqual(inspect['State']['ExitCode'], exitcode) + class TestLogs(BaseTestCase): def runTest(self): snippet = 'Flowering Nights (Sakuya Iyazoi)' @@ -200,6 +231,19 @@ class TestLogs(BaseTestCase): logs = self.client.logs(id) self.assertEqual(logs, snippet + '\n') +class TestLogsWithDictInsteadOfId(BaseTestCase): + def runTest(self): + snippet = 'Flowering Nights (Sakuya Iyazoi)' + container = self.client.create_container('busybox', + 'echo {0}'.format(snippet)) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + exitcode = self.client.wait(id) + self.assertEqual(exitcode, 0) + logs = self.client.logs(container) + self.assertEqual(logs, snippet + '\n') + class TestDiff(BaseTestCase): def runTest(self): container = self.client.create_container('busybox', ['touch', '/test']) @@ -214,6 +258,20 @@ class TestDiff(BaseTestCase): self.assertIn('Kind', test_diff[0]) self.assertEqual(test_diff[0]['Kind'], 1) +class TestDiffWithDictInsteadOfId(BaseTestCase): + def runTest(self): + container = self.client.create_container('busybox', ['touch', '/test']) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + exitcode = self.client.wait(id) + self.assertEqual(exitcode, 0) + diff = self.client.diff(container) + test_diff = [x for x in diff if x.get('Path', None) == '/test'] + self.assertEqual(len(test_diff), 1) + self.assertIn('Kind', test_diff[0]) + self.assertEqual(test_diff[0]['Kind'], 1) + class TestStop(BaseTestCase): def runTest(self): container = self.client.create_container('busybox', ['sleep', '9999']) @@ -229,6 +287,22 @@ class TestStop(BaseTestCase): self.assertIn('Running', state) self.assertEqual(state['Running'], False) +class TestStopWithDictInsteadOfId(BaseTestCase): + def runTest(self): + container = self.client.create_container('busybox', ['sleep', '9999']) + self.assertIn('Id', container) + id = container['Id'] + self.client.start(container) + self.tmp_containers.append(id) + self.client.stop(container, timeout=2) + container_info = self.client.inspect_container(id) + self.assertIn('State', container_info) + state = container_info['State'] + self.assertIn('ExitCode', state) + self.assertNotEqual(state['ExitCode'], 0) + self.assertIn('Running', state) + self.assertEqual(state['Running'], False) + class TestKill(BaseTestCase): def runTest(self): container = self.client.create_container('busybox', ['sleep', '9999']) @@ -244,6 +318,21 @@ class TestKill(BaseTestCase): self.assertIn('Running', state) self.assertEqual(state['Running'], False) +class TestKillWithDictInsteadOfId(BaseTestCase): + def runTest(self): + container = self.client.create_container('busybox', ['sleep', '9999']) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + self.client.kill(container) + container_info = self.client.inspect_container(id) + self.assertIn('State', container_info) + state = container_info['State'] + self.assertIn('ExitCode', state) + self.assertNotEqual(state['ExitCode'], 0) + self.assertIn('Running', state) + self.assertEqual(state['Running'], False) + class TestRestart(BaseTestCase): def runTest(self): container = self.client.create_container('busybox', ['sleep', '9999']) @@ -264,6 +353,27 @@ class TestRestart(BaseTestCase): self.assertEqual(info2['State']['Running'], True) self.client.kill(id) +class TestRestartWithDictInsteadOfId(BaseTestCase): + def runTest(self): + container = self.client.create_container('busybox', ['sleep', '9999']) + self.assertIn('Id', container) + id = container['Id'] + self.client.start(container) + self.tmp_containers.append(id) + info = self.client.inspect_container(id) + self.assertIn('State', info) + self.assertIn('StartedAt', info['State']) + start_time1 = info['State']['StartedAt'] + self.client.restart(container, timeout=2) + info2 = self.client.inspect_container(id) + self.assertIn('State', info2) + self.assertIn('StartedAt', info2['State']) + start_time2 = info2['State']['StartedAt'] + self.assertNotEqual(start_time1, start_time2) + self.assertIn('Running', info2['State']) + self.assertEqual(info2['State']['Running'], True) + self.client.kill(id) + class TestRemoveContainer(BaseTestCase): def runTest(self): container = self.client.create_container('busybox', ['true']) @@ -275,6 +385,17 @@ class TestRemoveContainer(BaseTestCase): res = [x for x in containers if 'Id' in x and x['Id'].startswith(id)] self.assertEqual(len(res), 0) +class TestRemoveContainerWithDictInsteadOfId(BaseTestCase): + def runTest(self): + container = self.client.create_container('busybox', ['true']) + id = container['Id'] + self.client.start(id) + self.client.wait(id) + self.client.remove_container(container) + containers = self.client.containers(all=True) + res = [x for x in containers if 'Id' in x and x['Id'].startswith(id)] + self.assertEqual(len(res), 0) + ################## ## IMAGES TESTS ## ##################