From ea087b7b151ee4b6697352a7e79425f8dbd5ba47 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Fri, 30 Aug 2013 09:35:09 -0400 Subject: [PATCH 1/5] added privileged container support --- README.md | 2 +- docker/client.py | 9 ++++++--- tests/test.py | 7 +++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2aa2735f..ec2e8f23 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Identical to the `docker commit` command. * `c.containers(quiet=False, all=False, trunc=True, latest=False, since=None, before=None, limit=-1)` Identical to the `docker ps` command. -* `c.create_container(image, command, hostname=None, user=None, detach=False, stdin_open=False, tty=False, mem_limit=0, ports=None, environment=None, dns=None, volumes=None, volumes_from=None)` +* `c.create_container(image, command, hostname=None, user=None, detach=False, stdin_open=False, tty=False, mem_limit=0, ports=None, environment=None, dns=None, volumes=None, volumes_from=None, privileged=False)` Creates a container that can then be `start`ed. Parameters are similar to those for the `docker run` command except it doesn't support the attach options (`-a`) diff --git a/docker/client.py b/docker/client.py index 5f850b44..d20bc069 100644 --- a/docker/client.py +++ b/docker/client.py @@ -100,7 +100,8 @@ class Client(requests.Session): def _container_config(self, image, command, hostname=None, user=None, detach=False, stdin_open=False, tty=False, mem_limit=0, ports=None, - environment=None, dns=None, volumes=None, volumes_from=None): + environment=None, dns=None, volumes=None, volumes_from=None, + privileged=False): if isinstance(command, six.string_types): command = shlex.split(str(command)) if isinstance(environment, dict): @@ -121,6 +122,7 @@ class Client(requests.Session): 'Image': image, 'Volumes': volumes, 'VolumesFrom': volumes_from, + 'Privileged': privileged, } def _mkbuildcontext(self, dockerfile): @@ -269,10 +271,11 @@ class Client(requests.Session): def create_container(self, image, command, hostname=None, user=None, detach=False, stdin_open=False, tty=False, mem_limit=0, ports=None, - environment=None, dns=None, volumes=None, volumes_from=None): + environment=None, dns=None, volumes=None, volumes_from=None, + privileged=False): config = self._container_config(image, command, hostname, user, detach, stdin_open, tty, mem_limit, ports, environment, dns, - volumes, volumes_from) + volumes, volumes_from, privileged) return self.create_container_from_config(config) def create_container_from_config(self, config): diff --git a/tests/test.py b/tests/test.py index 3cd6f36f..026238fe 100644 --- a/tests/test.py +++ b/tests/test.py @@ -131,6 +131,13 @@ class TestCreateContainerWithBinds(BaseTestCase): os.unlink(shared_file) self.assertIn(filename, logs) +class TestCreateContainerPrivileged(BaseTestCase): + def runTest(self): + res = self.client.create_container('busybox', 'true', privileged=True) + inspect = self.client.inspect_container(res['Id']) + self.assertIn('Config', inspect) + self.assertEqual(inspect['Config']['Privileged'], True) + class TestStartContainer(BaseTestCase): def runTest(self): res = self.client.create_container('busybox', 'true') From fdcc4d9a09b02d0632a392e62439ae6ebf4ca7cc Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Sun, 1 Sep 2013 01:01:57 -0400 Subject: [PATCH 2/5] Set AttachStd* attributes according to detach and stdin_open commands, analogously to the docker cli --- docker/client.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docker/client.py b/docker/client.py index d20bc069..ed598c27 100644 --- a/docker/client.py +++ b/docker/client.py @@ -106,6 +106,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, @@ -113,9 +125,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, From da3fe9cdaa696b52eaf7a6f7067e53d6e78daf5f Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Sun, 1 Sep 2013 01:02:40 -0400 Subject: [PATCH 3/5] attach_socket() method for just getting the HTTP socket --- docker/client.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/docker/client.py b/docker/client.py index d20bc069..dbdef791 100644 --- a/docker/client.py +++ b/docker/client.py @@ -197,18 +197,23 @@ class Client(requests.Session): f.close() return config_file - 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 + } + u = self._url("/containers/{0}/attach".format(container)) res = self.post(u, None, params=params, stream=True) # 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) From f032ecfe474eae6083545535e0e7a6dca971a5d4 Mon Sep 17 00:00:00 2001 From: Deni Bertovic Date: Wed, 4 Sep 2013 15:53:37 +0200 Subject: [PATCH 4/5] Fixed #33 - make client commands more consistent --- docker/client.py | 28 ++++++++++- tests/test.py | 121 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 2 deletions(-) diff --git a/docker/client.py b/docker/client.py index d20bc069..9185a4fd 100644 --- a/docker/client.py +++ b/docker/client.py @@ -203,6 +203,8 @@ class Client(requests.Session): '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) # hijack the underlying socket from requests, icky @@ -287,10 +289,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) return res.raw @@ -338,9 +344,11 @@ 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". @@ -348,6 +356,8 @@ class Client(requests.Session): def kill(self, *args): for name in args: + if isinstance(name, dict): + name = name.get('Id') url = self._url("/containers/{0}/kill".format(name)) self.post(url, None) @@ -369,6 +379,8 @@ class Client(requests.Session): return res def logs(self, container): + if isinstance(container, dict): + container = container.get('Id') params = { 'logs': 1, 'stdout': 1, @@ -378,6 +390,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))) json_ = res.json() s_port = str(private_port) @@ -416,6 +430,8 @@ class Client(requests.Session): 'v': 1 if kwargs.get('v', False) else 0 } for container in args: + if isinstance(container, dict): + container = container.get('Id') res = self.delete(self._url("/containers/" + container), params=params) if res.status_code >= 400: raise RuntimeError(res.text) @@ -429,6 +445,8 @@ class Client(requests.Session): 't': kwargs.get('timeout', 10) } for name in args: + if isinstance(name, dict): + name = name.get('Id') url = self._url("/containers/{0}/restart".format(name)) self.post(url, None, params=params) @@ -446,6 +464,8 @@ class Client(requests.Session): } for name in args: + if isinstance(name, dict): + name = name.get('Id') url = self._url("/containers/{0}/start".format(name)) self._post_json(url, start_config) @@ -454,6 +474,8 @@ class Client(requests.Session): 't': kwargs.get('timeout', 10) } for name in args: + if isinstance(name, dict): + name = name.get('Id') url = self._url("/containers/{0}/stop".format(name)) self.post(url, None, params=params) @@ -474,6 +496,8 @@ class Client(requests.Session): def wait(self, *args): result = [] for name in args: + if isinstance(name, dict): + name = name.get('Id') url = self._url("/containers/{0}/wait".format(name)) res = self.post(url, None, timeout=None) json_ = res.json() diff --git a/tests/test.py b/tests/test.py index 026238fe..6e1cf787 100644 --- a/tests/test.py +++ b/tests/test.py @@ -155,6 +155,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']) @@ -169,6 +186,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)' @@ -182,6 +213,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']) @@ -198,6 +242,20 @@ class TestDiff(BaseTestCase): # FIXME also test remove/modify # (need testcommit first) +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']) @@ -213,6 +271,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']) @@ -228,6 +302,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']) @@ -248,6 +337,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']) @@ -259,6 +369,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 ## ################## From 6c616792d4e25302a7eecb3c6d7df39e2ec3d395 Mon Sep 17 00:00:00 2001 From: Deni Bertovic Date: Wed, 4 Sep 2013 23:46:31 +0200 Subject: [PATCH 5/5] updated README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ec2e8f23..66a3f991 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,10 @@ 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. -* `c.inspect_image(container_id)` +* `c.inspect_image(image_id)` Identical to the `docker inspect` command, but can only be used with an image ID. * `c.kill(containers...)` @@ -86,11 +86,11 @@ Identical to the `docker restart` command. * `c.search(term)` Identical to the `docker search` command. -* `c.start(container)` +* `c.start(containers)` Identical to the `docker start` command, but doesn't support attach options. Use `docker logs` to recover `stdout`/`stderr` -* `c.start(container, binds={'/host': '/mnt'})` +* `c.start(containers, binds={'/host': '/mnt'})` Allows to bind a directory in the host to the container. Similar to the `docker run` command with the `-b="/host:/mnt"`. Requires the container to be created with the volumes argument: