Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,12 @@ $stream->on('close', function () {

See also the [streaming exec example](examples/exec-stream.php) and the [exec benchmark example](examples/benchmark-exec.php).

The TTY mode should be set depending on whether your command needs a TTY
or not. Note that toggling the TTY mode affects how/whether you can access
the STDERR stream and also has a significant impact on performance for
larger streams (relevant for 100 MiB and above). See also the TTY mode
on the `execStart*()` call.

Running this benchmark on my personal (rather mediocre) VM setup reveals that
the benchmark achieves a throughput of ~300 MiB/s while the (totally unfair)
comparison script using the plain Docker client only yields ~100 MiB/s.
Expand Down
2 changes: 1 addition & 1 deletion examples/benchmark-exec.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
$factory = new Factory($loop);
$client = $factory->createClient();

$client->execCreate($container, array('Cmd' => $cmd, 'AttachStdout' => true, 'AttachStderr' => true))->then(function ($info) use ($client) {
$client->execCreate($container, $cmd)->then(function ($info) use ($client) {
$stream = $client->execStartStream($info['Id'], true);

$start = microtime(true);
Expand Down
2 changes: 1 addition & 1 deletion examples/exec-inspect.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
$factory = new Factory($loop);
$client = $factory->createClient();

$client->execCreate($container, array('Cmd' => $cmd, 'AttachStdout' => true, 'AttachStderr' => true, 'Tty' => true))->then(function ($info) use ($client) {
$client->execCreate($container, $cmd, true)->then(function ($info) use ($client) {
echo 'Created with info: ' . json_encode($info) . PHP_EOL;

return $client->execInspect($info['Id']);
Expand Down
2 changes: 1 addition & 1 deletion examples/exec-stream.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
$out = new Stream(STDOUT, $loop);
$out->pause();

$client->execCreate($container, array('Cmd' => $cmd, 'AttachStdout' => true, 'AttachStderr' => true, 'Tty' => true))->then(function ($info) use ($client, $out) {
$client->execCreate($container, $cmd, true)->then(function ($info) use ($client, $out) {
$stream = $client->execStartStream($info['Id'], true);
$stream->pipe($out);

Expand Down
48 changes: 44 additions & 4 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -882,21 +882,61 @@ public function imageSearch($term)
/**
* Sets up an exec instance in a running container id
*
* @param string $container container ID
* @param array $config `array('Cmd' => 'date')` (see link)
* The $command should be given as an array of strings (the command plus
* arguments). Alternatively, you can also pass a single command line string
* which will then be wrapped in a shell process.
*
* The TTY mode should be set depending on whether your command needs a TTY
* or not. Note that toggling the TTY mode affects how/whether you can access
* the STDERR stream and also has a significant impact on performance for
* larger streams (relevant for 100 MiB and above). See also the TTY mode
* on the `execStart*()` call:
* - create=false, start=false:
* STDOUT/STDERR are multiplexed into separate streams + quite fast.
* This is the default mode, also for `docker exec`.
* - create=true, start=true:
* STDOUT and STDERR are mixed into a single stream + relatively slow.
* This is how `docker exec -t` works internally.
* - create=false, start=true
* STDOUT is streamed, STDERR can not be accessed at all + fastest mode.
* This looks strange to you? It probably is. See also the benchmarking example.
* - create=true, start=false
* STDOUT/STDERR are multiplexed into separate streams + relatively slow
* This looks strange to you? It probably is. Consider using the first option instead.
*
* @param string $container container ID
* @param string|array $cmd Command to run specified as an array of strings or a single command string
* @param boolean $tty TTY mode
* @param boolean $stdin attaches to STDIN of the exec command
* @param boolean $stdout attaches to STDOUT of the exec command
* @param boolean $stderr attaches to STDERR of the exec command
* @param string|int $user user-specific exec, otherwise defaults to main container user (requires API v1.19+ / Docker v1.7+)
* @param boolean $privileged privileged exec with all capabilities enabled (requires API v1.19+ / Docker v1.7+)
* @return PromiseInterface Promise<array> with exec ID in the form of `array("Id" => $execId)`
* @link https://docs.docker.com/reference/api/docker_remote_api_v1.15/#exec-create
*/
public function execCreate($container, $config)
public function execCreate($container, $cmd, $tty = false, $stdin = false, $stdout = true, $stderr = true, $user = '', $privileged = false)
{
if (!is_array($cmd)) {
$cmd = array('sh', '-c', (string)$cmd);
}

return $this->postJson(
$this->uri->expand(
'/containers/{container}/exec',
array(
'container' => $container
)
),
$config
array(
'Cmd' => $cmd,
'Tty' => !!$tty,
'AttachStdin' => !!$stdin,
'AttachStdout' => !!$stdout,
'AttachStderr' => !!$stderr,
'User' => $user,
'Privileged' => !!$privileged,
)
)->then(array($this->parser, 'expectJson'));
}

Expand Down
11 changes: 9 additions & 2 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -379,10 +379,17 @@ public function testImageSearch()
public function testExecCreate()
{
$json = array();
$config = array();
$this->expectRequestFlow('post', '/containers/123/exec', $this->createResponseJson($json), 'expectJson');

$this->expectPromiseResolveWith($json, $this->client->execCreate(123, $config));
$this->expectPromiseResolveWith($json, $this->client->execCreate(123, array('env')));
}

public function testExecCreateStringCommand()
{
$json = array();
$this->expectRequestFlow('post', '/containers/123/exec', $this->createResponseJson($json), 'expectJson');

$this->expectPromiseResolveWith($json, $this->client->execCreate(123, 'env'));
}

public function testExecDetached()
Expand Down
75 changes: 57 additions & 18 deletions tests/FunctionalClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,7 @@ public function testStartRunning()
*/
public function testExecCreateWhileRunning($container)
{
$promise = $this->client->execCreate($container, array(
'Cmd' => array('echo', '-n', 'hello', 'world'),
'AttachStdout' => true,
'AttachStderr' => true,
'Tty' => true
));
$promise = $this->client->execCreate($container, array('echo', '-n', 'hello', 'world'));
$exec = Block\await($promise, $this->loop);

$this->assertTrue(is_array($exec));
Expand Down Expand Up @@ -158,18 +153,67 @@ public function testExecInspectAfterRunning($exec)
$this->assertEquals(0, $info['ExitCode']);
}

/**
* @depends testStartRunning
* @param string $container
*/
public function testExecStringCommandWithOutputWhileRunning($container)
{
$promise = $this->client->execCreate($container, 'echo -n hello world');
$exec = Block\await($promise, $this->loop);

$this->assertTrue(is_array($exec));
$this->assertTrue(is_string($exec['Id']));

$promise = $this->client->execStart($exec['Id'], true);
$output = Block\await($promise, $this->loop);

$this->assertEquals('hello world', $output);
}

/**
* @depends testStartRunning
* @param string $container
*/
public function testExecUserSpecificCommandWithOutputWhileRunning($container)
{
$promise = $this->client->execCreate($container, 'whoami', true, false, true, true, 'nobody');
$exec = Block\await($promise, $this->loop);

$this->assertTrue(is_array($exec));
$this->assertTrue(is_string($exec['Id']));

$promise = $this->client->execStart($exec['Id'], true);
$output = Block\await($promise, $this->loop);

$this->assertEquals('nobody', rtrim($output));
}

/**
* @depends testStartRunning
* @param string $container
*/
public function testExecStringCommandWithStderrOutputWhileRunning($container)
{
$promise = $this->client->execCreate($container, 'echo -n hello world >&2', true);
$exec = Block\await($promise, $this->loop);

$this->assertTrue(is_array($exec));
$this->assertTrue(is_string($exec['Id']));

$promise = $this->client->execStart($exec['Id'], true);
$output = Block\await($promise, $this->loop);

$this->assertEquals('hello world', $output);
}

/**
* @depends testStartRunning
* @param string $container
*/
public function testExecStreamEmptyOutputWhileRunning($container)
{
$promise = $this->client->execCreate($container, array(
'Cmd' => array('true'),
'AttachStdout' => true,
'AttachStderr' => true,
'Tty' => true
));
$promise = $this->client->execCreate($container, array('true'));
$exec = Block\await($promise, $this->loop);

$this->assertTrue(is_array($exec));
Expand All @@ -189,12 +233,7 @@ public function testExecStreamEmptyOutputWhileRunning($container)
*/
public function testExecDetachedWhileRunning($container)
{
$promise = $this->client->execCreate($container, array(
'Cmd' => array('sleep', '10'),
'AttachStdout' => true,
'AttachStderr' => true,
'Tty' => true
));
$promise = $this->client->execCreate($container, array('sleep', '10'));
$exec = Block\await($promise, $this->loop);

$this->assertTrue(is_array($exec));
Expand Down