diff --git a/docker/client.py b/docker/client.py index f79ec7bb09..e4a673552a 100644 --- a/docker/client.py +++ b/docker/client.py @@ -280,19 +280,23 @@ def events(self, since=None, until=None, filters=None, decode=None): @check_resource def exec_create(self, container, cmd, stdout=True, stderr=True, tty=False, - privileged=False): + privileged=False, user=''): if utils.compare_version('1.15', self._version) < 0: raise errors.InvalidVersion('Exec is not supported in API < 1.15') if privileged and utils.compare_version('1.19', self._version) < 0: raise errors.InvalidVersion( 'Privileged exec is not supported in API < 1.19' ) + if user and utils.compare_version('1.19', self._version) < 0: + raise errors.InvalidVersion( + 'User-specific exec is not supported in API < 1.19' + ) if isinstance(cmd, six.string_types): cmd = shlex.split(str(cmd)) data = { 'Container': container, - 'User': '', + 'User': user, 'Privileged': privileged, 'Tty': tty, 'AttachStdin': False, diff --git a/docs/api.md b/docs/api.md index b9b29c5a10..b890b96944 100644 --- a/docs/api.md +++ b/docs/api.md @@ -303,6 +303,7 @@ Sets up an exec instance in a running container. * stdout (bool): Attach to stdout of the exec command if true. Default: True * stderr (bool): Attach to stderr of the exec command if true. Default: True * tty (bool): Allocate a pseudo-TTY. Default: False +* user (str): User to execute command as. Default: root **Returns** (dict): A dictionary with an exec 'Id' key. diff --git a/tests/integration_test.py b/tests/integration_test.py index 59919dabeb..9e4e9d9d16 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -844,6 +844,40 @@ def runTest(self): self.assertEqual(exec_log, expected) +@unittest.skipIf(not EXEC_DRIVER_IS_NATIVE, 'Exec driver not native') +class TestExecuteCommandStringAsUser(BaseTestCase): + def runTest(self): + container = self.client.create_container('busybox', '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') + self.assertIn('Id', res) + + exec_log = self.client.exec_start(res) + expected = b'default' if six.PY3 else 'default\n' + self.assertEqual(exec_log, expected) + + +@unittest.skipIf(not EXEC_DRIVER_IS_NATIVE, 'Exec driver not native') +class TestExecuteCommandStringAsRoot(BaseTestCase): + def runTest(self): + container = self.client.create_container('busybox', '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') + self.assertIn('Id', res) + + exec_log = self.client.exec_start(res) + expected = b'root' if six.PY3 else 'root\n' + self.assertEqual(exec_log, expected) + + @unittest.skipIf(not EXEC_DRIVER_IS_NATIVE, 'Exec driver not native') class TestExecuteCommandStreaming(BaseTestCase): def runTest(self):