Kaydet (Commit) 465e4d9a authored tarafından Joffrey F's avatar Joffrey F

git push origin masterMerge branch 'phensley-exec'

...@@ -34,7 +34,7 @@ from .tls import TLSConfig ...@@ -34,7 +34,7 @@ from .tls import TLSConfig
if not six.PY3: if not six.PY3:
import websocket import websocket
DEFAULT_DOCKER_API_VERSION = '1.12' DEFAULT_DOCKER_API_VERSION = '1.15'
DEFAULT_TIMEOUT_SECONDS = 60 DEFAULT_TIMEOUT_SECONDS = 60
STREAM_HEADER_SIZE_BYTES = 8 STREAM_HEADER_SIZE_BYTES = 8
...@@ -546,6 +546,48 @@ class Client(requests.Session): ...@@ -546,6 +546,48 @@ class Client(requests.Session):
def events(self): def events(self):
return self._stream_helper(self.get(self._url('/events'), stream=True)) 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): def export(self, container):
if isinstance(container, dict): if isinstance(container, dict):
container = container.get('Id') container = container.get('Id')
......
...@@ -221,11 +221,36 @@ Inspect changes on a container's filesystem ...@@ -221,11 +221,36 @@ Inspect changes on a container's filesystem
**Returns** (str): **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
Export the contents of a filesystem as a tar archive to STDOUT Export the contents of a filesystem as a tar archive to STDOUT
**Params**: **Params**:
* container (str): The container to export * container (str): The container to export
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
CURRENT_VERSION = 'v1.12' CURRENT_VERSION = 'v1.15'
FAKE_CONTAINER_ID = '3cc2351ab11b' FAKE_CONTAINER_ID = '3cc2351ab11b'
FAKE_IMAGE_ID = 'e9aa60c60128' FAKE_IMAGE_ID = 'e9aa60c60128'
...@@ -225,6 +225,20 @@ def get_fake_export(): ...@@ -225,6 +225,20 @@ def get_fake_export():
return status_code, response 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(): def post_fake_stop_container():
status_code = 200 status_code = 200
response = {'Id': FAKE_CONTAINER_ID} response = {'Id': FAKE_CONTAINER_ID}
...@@ -330,6 +344,10 @@ fake_responses = { ...@@ -330,6 +344,10 @@ fake_responses = {
get_fake_diff, get_fake_diff,
'{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):
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): '{1}/{0}/containers/3cc2351ab11b/stop'.format(CURRENT_VERSION, prefix):
post_fake_stop_container, post_fake_stop_container,
'{1}/{0}/containers/3cc2351ab11b/kill'.format(CURRENT_VERSION, prefix): '{1}/{0}/containers/3cc2351ab11b/kill'.format(CURRENT_VERSION, prefix):
......
...@@ -627,6 +627,49 @@ class TestRestartingContainer(BaseTestCase): ...@@ -627,6 +627,49 @@ class TestRestartingContainer(BaseTestCase):
res = [x for x in containers if 'Id' in x and x['Id'].startswith(id)] res = [x for x in containers if 'Id' in x and x['Id'].startswith(id)]
self.assertEqual(len(res), 0) 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 # # LINKS TESTS #
################# #################
......
...@@ -1088,6 +1088,31 @@ class DockerClientTest(Cleanup, unittest.TestCase): ...@@ -1088,6 +1088,31 @@ class DockerClientTest(Cleanup, unittest.TestCase):
timeout=(docker.client.DEFAULT_TIMEOUT_SECONDS + timeout) 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): def test_kill_container(self):
try: try:
self.client.kill(fake_api.FAKE_CONTAINER_ID) self.client.kill(fake_api.FAKE_CONTAINER_ID)
......
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