mirror of https://github.com/docker/docker-py.git
commit
5eeb61a1e5
|
|
@ -17,6 +17,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import struct
|
import struct
|
||||||
|
import warnings
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
@ -515,6 +516,17 @@ class Client(requests.Session):
|
||||||
@check_resource
|
@check_resource
|
||||||
def execute(self, container, cmd, detach=False, stdout=True, stderr=True,
|
def execute(self, container, cmd, detach=False, stdout=True, stderr=True,
|
||||||
stream=False, tty=False):
|
stream=False, tty=False):
|
||||||
|
warnings.warn(
|
||||||
|
'Client.execute is being deprecated. Please use exec_create & '
|
||||||
|
'exec_start instead', DeprecationWarning
|
||||||
|
)
|
||||||
|
create_res = self.exec_create(
|
||||||
|
container, cmd, detach, stdout, stderr, tty
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.exec_start(create_res, detach, tty, stream)
|
||||||
|
|
||||||
|
def exec_create(self, container, cmd, stdout=True, stderr=True, tty=False):
|
||||||
if utils.compare_version('1.15', self._version) < 0:
|
if utils.compare_version('1.15', self._version) < 0:
|
||||||
raise errors.InvalidVersion('Exec is not supported in API < 1.15')
|
raise errors.InvalidVersion('Exec is not supported in API < 1.15')
|
||||||
if isinstance(container, dict):
|
if isinstance(container, dict):
|
||||||
|
|
@ -530,18 +542,47 @@ class Client(requests.Session):
|
||||||
'AttachStdin': False,
|
'AttachStdin': False,
|
||||||
'AttachStdout': stdout,
|
'AttachStdout': stdout,
|
||||||
'AttachStderr': stderr,
|
'AttachStderr': stderr,
|
||||||
'Detach': detach,
|
|
||||||
'Cmd': cmd
|
'Cmd': cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
# create the command
|
|
||||||
url = self._url('/containers/{0}/exec'.format(container))
|
url = self._url('/containers/{0}/exec'.format(container))
|
||||||
res = self._post_json(url, data=data)
|
res = self._post_json(url, data=data)
|
||||||
self._raise_for_status(res)
|
return self._result(res, True)
|
||||||
|
|
||||||
# start the command
|
def exec_inspect(self, exec_id):
|
||||||
cmd_id = res.json().get('Id')
|
if utils.compare_version('1.15', self._version) < 0:
|
||||||
res = self._post_json(self._url('/exec/{0}/start'.format(cmd_id)),
|
raise errors.InvalidVersion('Exec is not supported in API < 1.15')
|
||||||
|
if isinstance(exec_id, dict):
|
||||||
|
exec_id = exec_id.get('Id')
|
||||||
|
res = self._get(self._url("/exec/{0}/json".format(exec_id)))
|
||||||
|
return self._result(res, True)
|
||||||
|
|
||||||
|
def exec_resize(self, exec_id, height=None, width=None):
|
||||||
|
if utils.compare_version('1.15', self._version) < 0:
|
||||||
|
raise errors.InvalidVersion('Exec is not supported in API < 1.15')
|
||||||
|
if isinstance(exec_id, dict):
|
||||||
|
exec_id = exec_id.get('Id')
|
||||||
|
data = {
|
||||||
|
'h': height,
|
||||||
|
'w': width
|
||||||
|
}
|
||||||
|
res = self._post_json(
|
||||||
|
self._url('/exec/{0}/resize'.format(exec_id)), data
|
||||||
|
)
|
||||||
|
res.raise_for_status()
|
||||||
|
|
||||||
|
def exec_start(self, exec_id, detach=False, tty=False, stream=False):
|
||||||
|
if utils.compare_version('1.15', self._version) < 0:
|
||||||
|
raise errors.InvalidVersion('Exec is not supported in API < 1.15')
|
||||||
|
if isinstance(exec_id, dict):
|
||||||
|
exec_id = exec_id.get('Id')
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'Tty': tty,
|
||||||
|
'Detach': detach
|
||||||
|
}
|
||||||
|
|
||||||
|
res = self._post_json(self._url('/exec/{0}/start'.format(exec_id)),
|
||||||
data=data, stream=stream)
|
data=data, stream=stream)
|
||||||
self._raise_for_status(res)
|
self._raise_for_status(res)
|
||||||
if stream:
|
if stream:
|
||||||
|
|
|
||||||
56
docs/api.md
56
docs/api.md
|
|
@ -256,28 +256,58 @@ function return a blocking generator you can iterate over to retrieve events as
|
||||||
|
|
||||||
## execute
|
## execute
|
||||||
|
|
||||||
```python
|
This command is deprecated for docker-py >= 1.2.0 ; use `exec_create` and
|
||||||
c.execute(container, cmd, detach=False, stdout=True, stderr=True,
|
`exec_start` instead.
|
||||||
stream=False, tty=False)
|
|
||||||
```
|
|
||||||
|
|
||||||
Execute a command in a running container.
|
## exec_create
|
||||||
|
|
||||||
|
Sets up an exec instance in a running container.
|
||||||
|
|
||||||
**Params**:
|
**Params**:
|
||||||
|
|
||||||
* container (str): can be a container dictionary (result of
|
* container (str): Target container where exec instance will be created
|
||||||
running `inspect_container`), unique id or container name.
|
* cmd (str or list): Command to be executed
|
||||||
|
* 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
|
||||||
|
|
||||||
|
**Returns** (dict): A dictionary with an exec 'Id' key.
|
||||||
|
|
||||||
|
|
||||||
* cmd (str or list): representing the command and its arguments.
|
## exec_inspect
|
||||||
|
|
||||||
* detach (bool): flag to `True` will run the process in the background.
|
Return low-level information about an exec command.
|
||||||
|
|
||||||
* stdout (bool): indicates which output streams to read from.
|
**Params**:
|
||||||
* stderr (bool): indicates which output streams to read from.
|
|
||||||
|
|
||||||
* stream (bool): indicates whether to return a generator which will yield
|
* exec_id (str): ID of the exec instance
|
||||||
the streaming response in chunks.
|
|
||||||
|
**Returns** (dict): Dictionary of values returned by the endpoint.
|
||||||
|
|
||||||
|
|
||||||
|
## exec_resize
|
||||||
|
|
||||||
|
Resize the tty session used by the specified exec command.
|
||||||
|
|
||||||
|
**Params**:
|
||||||
|
|
||||||
|
* exec_id (str): ID of the exec instance
|
||||||
|
* height (int): Height of tty session
|
||||||
|
* width (int): Width of tty session
|
||||||
|
|
||||||
|
## exec_start
|
||||||
|
|
||||||
|
Start a previously set up exec instance.
|
||||||
|
|
||||||
|
**Params**:
|
||||||
|
|
||||||
|
* exec_id (str): ID of the exec instance
|
||||||
|
* detach (bool): If true, detach from the exec command. Default: False
|
||||||
|
* tty (bool): Allocate a pseudo-TTY. Default: False
|
||||||
|
* stream (bool): Stream response data
|
||||||
|
|
||||||
|
**Returns** (generator or str): If `stream=True`, a generator yielding response
|
||||||
|
chunks. A string containing response data otherwise.
|
||||||
|
|
||||||
## export
|
## export
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ CURRENT_VERSION = 'v1.18'
|
||||||
|
|
||||||
FAKE_CONTAINER_ID = '3cc2351ab11b'
|
FAKE_CONTAINER_ID = '3cc2351ab11b'
|
||||||
FAKE_IMAGE_ID = 'e9aa60c60128'
|
FAKE_IMAGE_ID = 'e9aa60c60128'
|
||||||
|
FAKE_EXEC_ID = 'd5d177f121dc'
|
||||||
FAKE_IMAGE_NAME = 'test_image'
|
FAKE_IMAGE_NAME = 'test_image'
|
||||||
FAKE_TARBALL_PATH = '/path/to/tarball'
|
FAKE_TARBALL_PATH = '/path/to/tarball'
|
||||||
FAKE_REPO_NAME = 'repo'
|
FAKE_REPO_NAME = 'repo'
|
||||||
|
|
@ -247,13 +248,13 @@ def get_fake_export():
|
||||||
return status_code, response
|
return status_code, response
|
||||||
|
|
||||||
|
|
||||||
def post_fake_execute():
|
def post_fake_exec_create():
|
||||||
status_code = 200
|
status_code = 200
|
||||||
response = {'Id': FAKE_CONTAINER_ID}
|
response = {'Id': FAKE_EXEC_ID}
|
||||||
return status_code, response
|
return status_code, response
|
||||||
|
|
||||||
|
|
||||||
def post_fake_execute_start():
|
def post_fake_exec_start():
|
||||||
status_code = 200
|
status_code = 200
|
||||||
response = (b'\x01\x00\x00\x00\x00\x00\x00\x11bin\nboot\ndev\netc\n'
|
response = (b'\x01\x00\x00\x00\x00\x00\x00\x11bin\nboot\ndev\netc\n'
|
||||||
b'\x01\x00\x00\x00\x00\x00\x00\x12lib\nmnt\nproc\nroot\n'
|
b'\x01\x00\x00\x00\x00\x00\x00\x12lib\nmnt\nproc\nroot\n'
|
||||||
|
|
@ -261,6 +262,30 @@ def post_fake_execute_start():
|
||||||
return status_code, response
|
return status_code, response
|
||||||
|
|
||||||
|
|
||||||
|
def post_fake_exec_resize():
|
||||||
|
status_code = 201
|
||||||
|
return status_code, ''
|
||||||
|
|
||||||
|
|
||||||
|
def get_fake_exec_inspect():
|
||||||
|
return 200, {
|
||||||
|
'OpenStderr': True,
|
||||||
|
'OpenStdout': True,
|
||||||
|
'Container': get_fake_inspect_container()[1],
|
||||||
|
'Running': False,
|
||||||
|
'ProcessConfig': {
|
||||||
|
'arguments': ['hello world'],
|
||||||
|
'tty': False,
|
||||||
|
'entrypoint': 'echo',
|
||||||
|
'privileged': False,
|
||||||
|
'user': ''
|
||||||
|
},
|
||||||
|
'ExitCode': 0,
|
||||||
|
'ID': FAKE_EXEC_ID,
|
||||||
|
'OpenStdin': False
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def post_fake_stop_container():
|
def post_fake_stop_container():
|
||||||
status_code = 200
|
status_code = 200
|
||||||
response = {'Id': FAKE_CONTAINER_ID}
|
response = {'Id': FAKE_CONTAINER_ID}
|
||||||
|
|
@ -393,9 +418,14 @@ fake_responses = {
|
||||||
'{1}/{0}/containers/3cc2351ab11b/export'.format(CURRENT_VERSION, prefix):
|
'{1}/{0}/containers/3cc2351ab11b/export'.format(CURRENT_VERSION, prefix):
|
||||||
get_fake_export,
|
get_fake_export,
|
||||||
'{1}/{0}/containers/3cc2351ab11b/exec'.format(CURRENT_VERSION, prefix):
|
'{1}/{0}/containers/3cc2351ab11b/exec'.format(CURRENT_VERSION, prefix):
|
||||||
post_fake_execute,
|
post_fake_exec_create,
|
||||||
'{1}/{0}/exec/3cc2351ab11b/start'.format(CURRENT_VERSION, prefix):
|
'{1}/{0}/exec/d5d177f121dc/start'.format(CURRENT_VERSION, prefix):
|
||||||
post_fake_execute_start,
|
post_fake_exec_start,
|
||||||
|
'{1}/{0}/exec/d5d177f121dc/json'.format(CURRENT_VERSION, prefix):
|
||||||
|
get_fake_exec_inspect,
|
||||||
|
'{1}/{0}/exec/d5d177f121dc/resize'.format(CURRENT_VERSION, prefix):
|
||||||
|
post_fake_exec_resize,
|
||||||
|
|
||||||
'{1}/{0}/containers/3cc2351ab11b/stats'.format(CURRENT_VERSION, prefix):
|
'{1}/{0}/containers/3cc2351ab11b/stats'.format(CURRENT_VERSION, prefix):
|
||||||
get_fake_stats,
|
get_fake_stats,
|
||||||
'{1}/{0}/containers/3cc2351ab11b/stop'.format(CURRENT_VERSION, prefix):
|
'{1}/{0}/containers/3cc2351ab11b/stop'.format(CURRENT_VERSION, prefix):
|
||||||
|
|
|
||||||
|
|
@ -1071,9 +1071,12 @@ class TestExecuteCommand(BaseTestCase):
|
||||||
self.client.start(id)
|
self.client.start(id)
|
||||||
self.tmp_containers.append(id)
|
self.tmp_containers.append(id)
|
||||||
|
|
||||||
res = self.client.execute(id, ['echo', 'hello'])
|
res = self.client.exec_create(id, ['echo', 'hello'])
|
||||||
|
self.assertIn('Id', res)
|
||||||
|
|
||||||
|
exec_log = self.client.exec_start(res)
|
||||||
expected = b'hello\n' if six.PY3 else 'hello\n'
|
expected = b'hello\n' if six.PY3 else 'hello\n'
|
||||||
self.assertEqual(res, expected)
|
self.assertEqual(exec_log, expected)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(not EXEC_DRIVER_IS_NATIVE, 'Exec driver not native')
|
@unittest.skipIf(not EXEC_DRIVER_IS_NATIVE, 'Exec driver not native')
|
||||||
|
|
@ -1085,9 +1088,12 @@ class TestExecuteCommandString(BaseTestCase):
|
||||||
self.client.start(id)
|
self.client.start(id)
|
||||||
self.tmp_containers.append(id)
|
self.tmp_containers.append(id)
|
||||||
|
|
||||||
res = self.client.execute(id, 'echo hello world', stdout=True)
|
res = self.client.exec_create(id, 'echo hello world')
|
||||||
|
self.assertIn('Id', res)
|
||||||
|
|
||||||
|
exec_log = self.client.exec_start(res)
|
||||||
expected = b'hello world\n' if six.PY3 else 'hello world\n'
|
expected = b'hello world\n' if six.PY3 else 'hello world\n'
|
||||||
self.assertEqual(res, expected)
|
self.assertEqual(exec_log, expected)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(not EXEC_DRIVER_IS_NATIVE, 'Exec driver not native')
|
@unittest.skipIf(not EXEC_DRIVER_IS_NATIVE, 'Exec driver not native')
|
||||||
|
|
@ -1099,14 +1105,33 @@ class TestExecuteCommandStreaming(BaseTestCase):
|
||||||
self.client.start(id)
|
self.client.start(id)
|
||||||
self.tmp_containers.append(id)
|
self.tmp_containers.append(id)
|
||||||
|
|
||||||
chunks = self.client.execute(id, ['echo', 'hello\nworld'], stream=True)
|
exec_id = self.client.exec_create(id, ['echo', 'hello\nworld'])
|
||||||
|
self.assertIn('Id', exec_id)
|
||||||
|
|
||||||
res = b'' if six.PY3 else ''
|
res = b'' if six.PY3 else ''
|
||||||
for chunk in chunks:
|
for chunk in self.client.exec_start(exec_id, stream=True):
|
||||||
res += chunk
|
res += chunk
|
||||||
expected = b'hello\nworld\n' if six.PY3 else 'hello\nworld\n'
|
expected = b'hello\nworld\n' if six.PY3 else 'hello\nworld\n'
|
||||||
self.assertEqual(res, expected)
|
self.assertEqual(res, expected)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(not EXEC_DRIVER_IS_NATIVE, 'Exec driver not native')
|
||||||
|
class TestExecInspect(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)
|
||||||
|
|
||||||
|
exec_id = self.client.exec_create(id, ['mkdir', '/does/not/exist'])
|
||||||
|
self.assertIn('Id', exec_id)
|
||||||
|
self.client.exec_start(exec_id)
|
||||||
|
exec_info = self.client.exec_inspect(exec_id)
|
||||||
|
self.assertIn('ExitCode', exec_info)
|
||||||
|
self.assertNotEqual(exec_info['ExitCode'], 0)
|
||||||
|
|
||||||
|
|
||||||
class TestRunContainerStreaming(BaseTestCase):
|
class TestRunContainerStreaming(BaseTestCase):
|
||||||
def runTest(self):
|
def runTest(self):
|
||||||
container = self.client.create_container('busybox', '/bin/sh',
|
container = self.client.create_container('busybox', '/bin/sh',
|
||||||
|
|
|
||||||
|
|
@ -1583,31 +1583,95 @@ class DockerClientTest(Cleanup, base.BaseTestCase):
|
||||||
timeout=(docker.client.DEFAULT_TIMEOUT_SECONDS + timeout)
|
timeout=(docker.client.DEFAULT_TIMEOUT_SECONDS + timeout)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_execute_command(self):
|
def test_exec_create(self):
|
||||||
try:
|
try:
|
||||||
self.client.execute(fake_api.FAKE_CONTAINER_ID, ['ls', '-1'])
|
self.client.exec_create(fake_api.FAKE_CONTAINER_ID, ['ls', '-1'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.fail('Command should not raise exception: {0}'.format(e))
|
self.fail('Command should not raise exception: {0}'.format(e))
|
||||||
|
|
||||||
args = fake_request.call_args
|
args = fake_request.call_args
|
||||||
self.assertEqual(args[0][0],
|
self.assertEqual(
|
||||||
url_prefix + 'exec/3cc2351ab11b/start')
|
args[0][0], url_prefix + 'containers/{0}/exec'.format(
|
||||||
|
fake_api.FAKE_CONTAINER_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(json.loads(args[1]['data']),
|
self.assertEqual(
|
||||||
json.loads('''{
|
json.loads(args[1]['data']), {
|
||||||
"Tty": false,
|
'Tty': False,
|
||||||
"AttachStderr": true,
|
'AttachStdout': True,
|
||||||
"Container": "3cc2351ab11b",
|
'Container': fake_api.FAKE_CONTAINER_ID,
|
||||||
"Cmd": ["ls", "-1"],
|
'Cmd': ['ls', '-1'],
|
||||||
"AttachStdin": false,
|
'Privileged': False,
|
||||||
"User": "",
|
'AttachStdin': False,
|
||||||
"Detach": false,
|
'AttachStderr': True,
|
||||||
"Privileged": false,
|
'User': ''
|
||||||
"AttachStdout": true}'''))
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(args[1]['headers'],
|
self.assertEqual(args[1]['headers'],
|
||||||
{'Content-Type': 'application/json'})
|
{'Content-Type': 'application/json'})
|
||||||
|
|
||||||
|
def test_exec_start(self):
|
||||||
|
try:
|
||||||
|
self.client.exec_start(fake_api.FAKE_EXEC_ID)
|
||||||
|
except Exception as e:
|
||||||
|
self.fail('Command should not raise exception: {0}'.format(e))
|
||||||
|
|
||||||
|
args = fake_request.call_args
|
||||||
|
self.assertEqual(
|
||||||
|
args[0][0], url_prefix + 'exec/{0}/start'.format(
|
||||||
|
fake_api.FAKE_EXEC_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
json.loads(args[1]['data']), {
|
||||||
|
'Tty': False,
|
||||||
|
'Detach': False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(args[1]['headers'],
|
||||||
|
{'Content-Type': 'application/json'})
|
||||||
|
|
||||||
|
def test_exec_inspect(self):
|
||||||
|
try:
|
||||||
|
self.client.exec_inspect(fake_api.FAKE_EXEC_ID)
|
||||||
|
except Exception as e:
|
||||||
|
self.fail('Command should not raise exception: {0}'.format(e))
|
||||||
|
|
||||||
|
args = fake_request.call_args
|
||||||
|
self.assertEqual(
|
||||||
|
args[0][0], url_prefix + 'exec/{0}/json'.format(
|
||||||
|
fake_api.FAKE_EXEC_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_exec_resize(self):
|
||||||
|
try:
|
||||||
|
self.client.exec_resize(fake_api.FAKE_EXEC_ID, height=20, width=60)
|
||||||
|
except Exception as e:
|
||||||
|
self.fail('Command should not raise exception: {0}'.format(e))
|
||||||
|
|
||||||
|
args = fake_request.call_args
|
||||||
|
self.assertEqual(
|
||||||
|
args[0][0], url_prefix + 'exec/{0}/resize'.format(
|
||||||
|
fake_api.FAKE_EXEC_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
json.loads(args[1]['data']), {
|
||||||
|
'h': 20,
|
||||||
|
'w': 60,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
args[1]['headers'], {'Content-Type': 'application/json'}
|
||||||
|
)
|
||||||
|
|
||||||
def test_pause_container(self):
|
def test_pause_container(self):
|
||||||
try:
|
try:
|
||||||
self.client.pause(fake_api.FAKE_CONTAINER_ID)
|
self.client.pause(fake_api.FAKE_CONTAINER_ID)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue