mirror of https://github.com/docker/docker-py.git
Merge branch 'exec' of github.com:phensley/docker-py into phensley-exec
Conflicts: README.md
This commit is contained in:
commit
e0d6b267ee
|
|
@ -34,7 +34,7 @@ from .tls import TLSConfig
|
|||
if not six.PY3:
|
||||
import websocket
|
||||
|
||||
DEFAULT_DOCKER_API_VERSION = '1.12'
|
||||
DEFAULT_DOCKER_API_VERSION = '1.15'
|
||||
DEFAULT_TIMEOUT_SECONDS = 60
|
||||
STREAM_HEADER_SIZE_BYTES = 8
|
||||
|
||||
|
|
@ -546,6 +546,48 @@ class Client(requests.Session):
|
|||
def events(self):
|
||||
return self._stream_helper(self.get(self._url('/events'), stream=True))
|
||||
|
||||
def execute(self, container, cmd, detach=False, stdout=True, stderr=True,
|
||||
stream=False, tty=False):
|
||||
if utils.compare_version('1.15', self._version) < 0:
|
||||
raise Exception('Exec is not supported in API < 1.15!')
|
||||
if isinstance(container, dict):
|
||||
container = container.get('Id')
|
||||
if isinstance(cmd, six.string_types):
|
||||
cmd = shlex.split(str(cmd))
|
||||
|
||||
data = {
|
||||
'Container': container,
|
||||
'User': '',
|
||||
'Privileged': False,
|
||||
'Tty': tty,
|
||||
'AttachStdin': False,
|
||||
'AttachStdout': stdout,
|
||||
'AttachStderr': stderr,
|
||||
'Detach': detach,
|
||||
'Cmd': cmd
|
||||
}
|
||||
|
||||
# create the command
|
||||
url = self._url('/containers/{0}/exec'.format(container))
|
||||
res = self._post_json(url, data=data)
|
||||
self._raise_for_status(res)
|
||||
|
||||
# start the command
|
||||
cmd_id = res.json().get('Id')
|
||||
res = self._post_json(self._url('/exec/{0}/start'.format(cmd_id)),
|
||||
data=data, stream=stream)
|
||||
self._raise_for_status(res)
|
||||
if stream:
|
||||
return self._multiplexed_socket_stream_helper(res)
|
||||
elif six.PY3:
|
||||
return bytes().join(
|
||||
[x for x in self._multiplexed_buffer_helper(res)]
|
||||
)
|
||||
else:
|
||||
return str().join(
|
||||
[x for x in self._multiplexed_buffer_helper(res)]
|
||||
)
|
||||
|
||||
def export(self, container):
|
||||
if isinstance(container, dict):
|
||||
container = container.get('Id')
|
||||
|
|
|
|||
27
docs/api.md
27
docs/api.md
|
|
@ -221,11 +221,36 @@ Inspect changes on a container's filesystem
|
|||
|
||||
**Returns** (str):
|
||||
|
||||
## exec
|
||||
|
||||
```python
|
||||
c.exec(container, cmd, detach=False, stdout=True, stderr=True,
|
||||
stream=False, tty=False)
|
||||
```
|
||||
|
||||
Execute a command in a running container.
|
||||
|
||||
**Params**:
|
||||
|
||||
* container (str): can be a container dictionary (result of
|
||||
running `inspect_container`), unique id or container name.
|
||||
|
||||
|
||||
* cmd (str or list): representing the command and its arguments.
|
||||
|
||||
* detach (bool): flag to `True` will run the process in the background.
|
||||
|
||||
* stdout (bool): indicates which output streams to read from.
|
||||
* stderr (bool): indicates which output streams to read from.
|
||||
|
||||
* stream (bool): indicates whether to return a generator which will yield
|
||||
the streaming response in chunks.
|
||||
|
||||
## export
|
||||
|
||||
Export the contents of a filesystem as a tar archive to STDOUT
|
||||
|
||||
**Params**:
|
||||
**Params**:
|
||||
|
||||
* container (str): The container to export
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
CURRENT_VERSION = 'v1.12'
|
||||
CURRENT_VERSION = 'v1.15'
|
||||
|
||||
FAKE_CONTAINER_ID = '3cc2351ab11b'
|
||||
FAKE_IMAGE_ID = 'e9aa60c60128'
|
||||
|
|
@ -225,6 +225,20 @@ def get_fake_export():
|
|||
return status_code, response
|
||||
|
||||
|
||||
def post_fake_execute():
|
||||
status_code = 200
|
||||
response = {'Id': FAKE_CONTAINER_ID}
|
||||
return status_code, response
|
||||
|
||||
|
||||
def post_fake_execute_start():
|
||||
status_code = 200
|
||||
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\x0csbin\nusr\nvar\n')
|
||||
return status_code, response
|
||||
|
||||
|
||||
def post_fake_stop_container():
|
||||
status_code = 200
|
||||
response = {'Id': FAKE_CONTAINER_ID}
|
||||
|
|
@ -330,6 +344,10 @@ fake_responses = {
|
|||
get_fake_diff,
|
||||
'{1}/{0}/containers/3cc2351ab11b/export'.format(CURRENT_VERSION, prefix):
|
||||
get_fake_export,
|
||||
'{1}/{0}/containers/3cc2351ab11b/exec'.format(CURRENT_VERSION, prefix):
|
||||
post_fake_execute,
|
||||
'{1}/{0}/exec/3cc2351ab11b/start'.format(CURRENT_VERSION, prefix):
|
||||
post_fake_execute_start,
|
||||
'{1}/{0}/containers/3cc2351ab11b/stop'.format(CURRENT_VERSION, prefix):
|
||||
post_fake_stop_container,
|
||||
'{1}/{0}/containers/3cc2351ab11b/kill'.format(CURRENT_VERSION, prefix):
|
||||
|
|
|
|||
|
|
@ -627,6 +627,49 @@ class TestRestartingContainer(BaseTestCase):
|
|||
res = [x for x in containers if 'Id' in x and x['Id'].startswith(id)]
|
||||
self.assertEqual(len(res), 0)
|
||||
|
||||
|
||||
class TestExecuteCommand(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.execute(id, ['echo', 'hello'])
|
||||
expected = b'hello\n' if six.PY3 else 'hello\n'
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
|
||||
class TestExecuteCommandString(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.execute(id, 'echo hello world', stdout=True)
|
||||
expected = b'hello world\n' if six.PY3 else 'hello world\n'
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
|
||||
class TestExecuteCommandStreaming(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)
|
||||
|
||||
chunks = self.client.execute(id, ['echo', 'hello\nworld'], stream=True)
|
||||
res = b'' if six.PY3 else ''
|
||||
for chunk in chunks:
|
||||
res += chunk
|
||||
expected = b'hello\nworld\n' if six.PY3 else 'hello\nworld\n'
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
|
||||
#################
|
||||
# LINKS TESTS #
|
||||
#################
|
||||
|
|
|
|||
|
|
@ -1088,6 +1088,31 @@ class DockerClientTest(Cleanup, unittest.TestCase):
|
|||
timeout=(docker.client.DEFAULT_TIMEOUT_SECONDS + timeout)
|
||||
)
|
||||
|
||||
def test_execute_command(self):
|
||||
try:
|
||||
self.client.execute(fake_api.FAKE_CONTAINER_ID, ['ls', '-1'])
|
||||
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/3cc2351ab11b/start')
|
||||
|
||||
self.assertEqual(json.loads(args[1]['data']),
|
||||
json.loads('''{
|
||||
"Tty": false,
|
||||
"AttachStderr": true,
|
||||
"Container": "3cc2351ab11b",
|
||||
"Cmd": ["ls", "-1"],
|
||||
"AttachStdin": false,
|
||||
"User": "",
|
||||
"Detach": false,
|
||||
"Privileged": false,
|
||||
"AttachStdout": true}'''))
|
||||
|
||||
self.assertEqual(args[1]['headers'],
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
def test_kill_container(self):
|
||||
try:
|
||||
self.client.kill(fake_api.FAKE_CONTAINER_ID)
|
||||
|
|
|
|||
Loading…
Reference in New Issue