Kaydet (Commit) a9a538ab authored tarafından Tomas Tomecek's avatar Tomas Tomecek

allow interactive exec

Signed-off-by: 's avatarTomas Tomecek <ttomecek@redhat.com>
üst 2c3af6ca
...@@ -7,8 +7,8 @@ from .. import utils ...@@ -7,8 +7,8 @@ from .. import utils
class ExecApiMixin(object): class ExecApiMixin(object):
@utils.minimum_version('1.15') @utils.minimum_version('1.15')
@utils.check_resource @utils.check_resource
def exec_create(self, container, cmd, stdout=True, stderr=True, tty=False, def exec_create(self, container, cmd, stdout=True, stderr=True,
privileged=False, user=''): stdin=False, tty=False, privileged=False, user=''):
if privileged and utils.compare_version('1.19', self._version) < 0: if privileged and utils.compare_version('1.19', self._version) < 0:
raise errors.InvalidVersion( raise errors.InvalidVersion(
'Privileged exec is not supported in API < 1.19' 'Privileged exec is not supported in API < 1.19'
...@@ -25,7 +25,7 @@ class ExecApiMixin(object): ...@@ -25,7 +25,7 @@ class ExecApiMixin(object):
'User': user, 'User': user,
'Privileged': privileged, 'Privileged': privileged,
'Tty': tty, 'Tty': tty,
'AttachStdin': False, 'AttachStdin': stdin,
'AttachStdout': stdout, 'AttachStdout': stdout,
'AttachStderr': stderr, 'AttachStderr': stderr,
'Cmd': cmd 'Cmd': cmd
...@@ -53,7 +53,11 @@ class ExecApiMixin(object): ...@@ -53,7 +53,11 @@ class ExecApiMixin(object):
self._raise_for_status(res) self._raise_for_status(res)
@utils.minimum_version('1.15') @utils.minimum_version('1.15')
def exec_start(self, exec_id, detach=False, tty=False, stream=False): def exec_start(self, exec_id, detach=False, tty=False, stream=False,
socket=False):
# we want opened socket if socket == True
if socket:
stream = True
if isinstance(exec_id, dict): if isinstance(exec_id, dict):
exec_id = exec_id.get('Id') exec_id = exec_id.get('Id')
...@@ -65,4 +69,7 @@ class ExecApiMixin(object): ...@@ -65,4 +69,7 @@ class ExecApiMixin(object):
res = self._post_json( res = self._post_json(
self._url('/exec/{0}/start', exec_id), data=data, stream=stream self._url('/exec/{0}/start', exec_id), data=data, stream=stream
) )
if socket:
return self._get_raw_response_socket(res)
return self._get_result_tty(stream, res, tty) return self._get_result_tty(stream, res, tty)
...@@ -4,7 +4,7 @@ from .utils import ( ...@@ -4,7 +4,7 @@ from .utils import (
kwargs_from_env, convert_filters, datetime_to_timestamp, create_host_config, kwargs_from_env, convert_filters, datetime_to_timestamp, create_host_config,
create_container_config, parse_bytes, ping_registry, parse_env_file, create_container_config, parse_bytes, ping_registry, parse_env_file,
version_lt, version_gte, decode_json_header, split_command, version_lt, version_gte, decode_json_header, split_command,
) # flake8: noqa ) # flake8: noqa
from .types import Ulimit, LogConfig # flake8: noqa from .types import Ulimit, LogConfig # flake8: noqa
from .decorators import check_resource, minimum_version #flake8: noqa from .decorators import check_resource, minimum_version # flake8: noqa
import errno
import os import os
import os.path import os.path
import select
import shutil import shutil
import struct
import tarfile import tarfile
import tempfile import tempfile
import unittest import unittest
...@@ -64,6 +67,49 @@ def docker_client_kwargs(**kwargs): ...@@ -64,6 +67,49 @@ def docker_client_kwargs(**kwargs):
return client_kwargs return client_kwargs
def read_socket(socket, n=4096):
""" Code stolen from dockerpty to read the socket """
recoverable_errors = (errno.EINTR, errno.EDEADLK, errno.EWOULDBLOCK)
# wait for data to become available
select.select([socket], [], [])
try:
if hasattr(socket, 'recv'):
return socket.recv(n)
return os.read(socket.fileno(), n)
except EnvironmentError as e:
if e.errno not in recoverable_errors:
raise
def next_packet_size(socket):
""" Code stolen from dockerpty to get the next packet size """
data = six.binary_type()
while len(data) < 8:
next_data = read_socket(socket, 8 - len(data))
if not next_data:
return 0
data = data + next_data
if data is None:
return 0
if len(data) == 8:
_, actual = struct.unpack('>BxxxL', data)
return actual
def read_data(socket, packet_size):
data = six.binary_type()
while len(data) < packet_size:
next_data = read_socket(socket, packet_size - len(data))
if not next_data:
assert False, "Failed trying to read in the dataz"
data += next_data
return data
class BaseTestCase(unittest.TestCase): class BaseTestCase(unittest.TestCase):
tmp_imgs = [] tmp_imgs = []
tmp_containers = [] tmp_containers = []
......
import errno
import os import os
import shutil import shutil
import signal import signal
import struct
import tempfile import tempfile
import docker import docker
...@@ -957,7 +955,8 @@ class AttachContainerTest(helpers.BaseTestCase): ...@@ -957,7 +955,8 @@ class AttachContainerTest(helpers.BaseTestCase):
def test_run_container_reading_socket(self): def test_run_container_reading_socket(self):
line = 'hi there and stuff and things, words!' line = 'hi there and stuff and things, words!'
command = "echo '{0}'".format(line) # `echo` appends CRLF, `printf` doesn't
command = "printf '{0}'".format(line)
container = self.client.create_container(BUSYBOX, command, container = self.client.create_container(BUSYBOX, command,
detach=True, tty=False) detach=True, tty=False)
ident = container['Id'] ident = container['Id']
...@@ -965,51 +964,14 @@ class AttachContainerTest(helpers.BaseTestCase): ...@@ -965,51 +964,14 @@ class AttachContainerTest(helpers.BaseTestCase):
opts = {"stdout": 1, "stream": 1, "logs": 1} opts = {"stdout": 1, "stream": 1, "logs": 1}
pty_stdout = self.client.attach_socket(ident, opts) pty_stdout = self.client.attach_socket(ident, opts)
self.addCleanup(pty_stdout.close)
self.client.start(ident) self.client.start(ident)
recoverable_errors = (errno.EINTR, errno.EDEADLK, errno.EWOULDBLOCK) next_size = helpers.next_packet_size(pty_stdout)
self.assertEqual(next_size, len(line))
def read(n=4096): data = helpers.read_data(pty_stdout, next_size)
"""Code stolen from dockerpty to read the socket""" self.assertEqual(data.decode('utf-8'), line)
try:
if hasattr(pty_stdout, 'recv'):
return pty_stdout.recv(n)
return os.read(pty_stdout.fileno(), n)
except EnvironmentError as e:
if e.errno not in recoverable_errors:
raise
def next_packet_size():
"""Code stolen from dockerpty to get the next packet size"""
data = six.binary_type()
while len(data) < 8:
next_data = read(8 - len(data))
if not next_data:
return 0
data = data + next_data
if data is None:
return 0
if len(data) == 8:
_, actual = struct.unpack('>BxxxL', data)
return actual
next_size = next_packet_size()
self.assertEqual(next_size, len(line) + 1)
data = six.binary_type()
while len(data) < next_size:
next_data = read(next_size - len(data))
if not next_data:
assert False, "Failed trying to read in the dataz"
data += next_data
self.assertEqual(data.decode('utf-8'), "{0}\n".format(line))
pty_stdout.close()
# Prevent segfault at the end of the test run
if hasattr(pty_stdout, "_response"):
del pty_stdout._response
class PauseTest(helpers.BaseTestCase): class PauseTest(helpers.BaseTestCase):
......
...@@ -77,8 +77,8 @@ class ExecTest(helpers.BaseTestCase): ...@@ -77,8 +77,8 @@ class ExecTest(helpers.BaseTestCase):
container = self.client.create_container(BUSYBOX, 'cat', container = self.client.create_container(BUSYBOX, 'cat',
detach=True, stdin_open=True) detach=True, stdin_open=True)
id = container['Id'] id = container['Id']
self.client.start(id)
self.tmp_containers.append(id) self.tmp_containers.append(id)
self.client.start(id)
exec_id = self.client.exec_create(id, ['echo', 'hello\nworld']) exec_id = self.client.exec_create(id, ['echo', 'hello\nworld'])
self.assertIn('Id', exec_id) self.assertIn('Id', exec_id)
...@@ -88,6 +88,30 @@ class ExecTest(helpers.BaseTestCase): ...@@ -88,6 +88,30 @@ class ExecTest(helpers.BaseTestCase):
res += chunk res += chunk
self.assertEqual(res, b'hello\nworld\n') self.assertEqual(res, b'hello\nworld\n')
def test_exec_start_socket(self):
if not helpers.exec_driver_is_native():
pytest.skip('Exec driver not native')
container = self.client.create_container(BUSYBOX, 'cat',
detach=True, stdin_open=True)
container_id = container['Id']
self.client.start(container_id)
self.tmp_containers.append(container_id)
line = 'yay, interactive exec!'
# `echo` appends CRLF, `printf` doesn't
exec_id = self.client.exec_create(
container_id, ['printf', line], tty=True)
self.assertIn('Id', exec_id)
socket = self.client.exec_start(exec_id, socket=True)
self.addCleanup(socket.close)
next_size = helpers.next_packet_size(socket)
self.assertEqual(next_size, len(line))
data = helpers.read_data(socket, next_size)
self.assertEqual(data.decode('utf-8'), line)
def test_exec_inspect(self): def test_exec_inspect(self):
if not helpers.exec_driver_is_native(): if not helpers.exec_driver_is_native():
pytest.skip('Exec driver not native') pytest.skip('Exec driver not native')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment