Skip to content

Commit 38c4e81

Browse files
committed
Fix custom $stderrEvent for container attach and logs streams
1 parent 3857d75 commit 38c4e81

5 files changed

Lines changed: 135 additions & 4 deletions

File tree

‎src/Client.php‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ public function containerLogsStream($container, $follow = false, $stdout = true,
374374
}
375375

376376
return $stream;
377-
}));
377+
}), $stderrEvent);
378378
}
379379

380380
/**
@@ -738,7 +738,7 @@ public function containerAttachStream($container, $logs = false, $stream = false
738738
}
739739

740740
return $stream;
741-
}));
741+
}), $stderrEvent);
742742
}
743743

744744
/**

‎src/Io/ReadableUnwrapStream.php‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ class ReadableUnwrapStream extends EventEmitter implements ReadableStreamInterfa
2727

2828
/**
2929
* @param PromiseInterface<ReadableStreamInterface> $promise
30+
* @param ?string $stderrEvent custom event to forward for STDERR data (otherwise only forwards "data")
3031
*/
31-
public function __construct(PromiseInterface $promise)
32+
public function __construct(PromiseInterface $promise, $stderrEvent = null)
3233
{
3334
$this->promise = $promise;
3435

@@ -37,7 +38,7 @@ public function __construct(PromiseInterface $promise)
3738
$paused =& $this->paused;
3839

3940
$promise->then(
40-
function (ReadableStreamInterface $stream) use ($out, &$input, &$paused) {
41+
function (ReadableStreamInterface $stream) use ($out, &$input, &$paused, $stderrEvent) {
4142
// output already closed while waiting or input already closed => close other side
4243
if (!$out->isReadable() || !$stream->isReadable()) {
4344
$stream->close();
@@ -55,6 +56,13 @@ function (ReadableStreamInterface $stream) use ($out, &$input, &$paused) {
5556
$out->emit('data', array($data));
5657
});
5758

59+
// forward custom STDERR event if given
60+
if ($stderrEvent !== null && !in_array($stderrEvent, array('data', 'end', 'error', 'close'), true)) {
61+
$stream->on($stderrEvent, function ($data) use ($out, $stderrEvent) {
62+
$out->emit($stderrEvent, array($data));
63+
});
64+
}
65+
5866
// forward end event to output and close
5967
$stream->on('end', function () use ($out) {
6068
$out->emit('end');

‎tests/ClientTest.php‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,25 @@ public function testContainerLogsStreamReturnStreamWhenInspectingContainerResolv
242242
$response->write('output');
243243
}
244244

245+
public function testContainerLogsStreamWithCustomStderrEventReturnStreamThatEmitsCustomStderrEventWhenInspectingContainerResolvesWithoutTtyAndContainerLogsRequestResolves()
246+
{
247+
$this->browser->expects($this->once())->method('get')->with('containers/123/json')->willReturn(\React\Promise\resolve(new Response(200, array(), '{"Config":{"Tty":false}}')));
248+
$this->browser->expects($this->once())->method('requestStreaming')->with('GET', 'containers/123/logs?stdout=1&stderr=1')->willReturn(\React\Promise\resolve(new Response(200, array(), '')));
249+
250+
$response = new ThroughStream();
251+
$demultiplexed = new ThroughStream();
252+
$this->parser->expects($this->once())->method('expectJson')->willReturn(array('Config' => array('Tty' => false)));
253+
$this->streamingParser->expects($this->once())->method('parsePlainStream')->willReturn($response);
254+
$this->streamingParser->expects($this->once())->method('demultiplexStream')->with($response, 'stderr')->willReturn($demultiplexed);
255+
256+
$stream = $this->client->containerLogsStream('123', false, true, true, 0, false, null, 'stderr');
257+
258+
$stream->on('data', $this->expectCallableOnceWith('output'));
259+
$stream->on('stderr', $this->expectCallableOnceWith('error'));
260+
$demultiplexed->write('output');
261+
$demultiplexed->emit('stderr', array('error'));
262+
}
263+
245264
public function testContainerExport()
246265
{
247266
$data = 'tar stream';
@@ -437,6 +456,25 @@ public function testContainerAttachStreamReturnStreamWhenInspectingContainerReso
437456
$response->write('output');
438457
}
439458

459+
public function testContainerAttachStreamWithCustomStderrEventReturnStreamThatEmitsCustomStderrEventWhenInspectingContainerResolvesWithoutTtyAndContainerAttachRequestResolves()
460+
{
461+
$this->browser->expects($this->once())->method('get')->with('containers/123/json')->willReturn(\React\Promise\resolve(new Response(200, array(), '{"Config":{"Tty":false}}')));
462+
$this->browser->expects($this->once())->method('requestStreaming')->with('POST', 'containers/123/attach?logs=1&stream=1&stdout=1&stderr=1')->willReturn(\React\Promise\resolve(new Response(200, array(), '')));
463+
464+
$response = new ThroughStream();
465+
$demultiplexed = new ThroughStream();
466+
$this->parser->expects($this->once())->method('expectJson')->willReturn(array('Config' => array('Tty' => false)));
467+
$this->streamingParser->expects($this->once())->method('parsePlainStream')->willReturn($response);
468+
$this->streamingParser->expects($this->once())->method('demultiplexStream')->with($response, 'stderr')->willReturn($demultiplexed);
469+
470+
$stream = $this->client->containerAttachStream('123', true, true, true, true, 'stderr');
471+
472+
$stream->on('data', $this->expectCallableOnceWith('output'));
473+
$stream->on('stderr', $this->expectCallableOnceWith('error'));
474+
$demultiplexed->write('output');
475+
$demultiplexed->emit('stderr', array('error'));
476+
}
477+
440478
public function testContainerRemove()
441479
{
442480
$this->expectRequestFlow('delete', 'containers/123', $this->createResponse(), 'expectEmpty');

‎tests/FunctionalClientTest.php‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,45 @@ public function testCreateStartAndRemoveContainer()
108108
$this->assertEquals(array('start', 'die', 'attach', 'destroy'), $actions);
109109
}
110110

111+
/**
112+
* @depends testImageInspectCheckIfBusyboxExists
113+
*/
114+
public function testCreateStartAndRemoveContainerWithStderrOutputEmitsCustomEventForLogsAndAttachStreams()
115+
{
116+
$config = array(
117+
'Image' => 'busybox',
118+
'Cmd' => array('sh', '-c', 'echo out; echo err >&2')
119+
);
120+
121+
$promise = $this->client->containerCreate($config);
122+
$container = \React\Async\await($promise);
123+
124+
$promise = $this->client->containerStart($container['Id']);
125+
\React\Async\await($promise);
126+
127+
$promise = $this->client->containerWait($container['Id']);
128+
\React\Async\await($promise);
129+
130+
$stream = $this->client->containerLogsStream($container['Id'], false, true, true, 0, false, null, 'err');
131+
$stream->on('err', $this->expectCallableOnceWith("err\n"));
132+
$stream->on('end', $this->expectCallableOnce());
133+
134+
$output = \React\Async\await($this->parser->bufferedStream($stream));
135+
136+
$this->assertEquals("out\n", $output);
137+
138+
$stream = $this->client->containerAttachStream($container['Id'], true, false, true, true, 'err');
139+
$stream->on('err', $this->expectCallableOnceWith("err\n"));
140+
$stream->on('end', $this->expectCallableOnce());
141+
142+
$output = \React\Async\await($this->parser->bufferedStream($stream));
143+
144+
$this->assertEquals("out\n", $output);
145+
146+
$promise = $this->client->containerRemove($container['Id'], false, true);
147+
\React\Async\await($promise);
148+
}
149+
111150
/**
112151
* @depends testImageInspectCheckIfBusyboxExists
113152
*/

‎tests/Io/ReadableUnwrapStreamTest.php‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,52 @@ public function testEmitsDataWhenInputEmitsData()
123123
$input->emit('data', array('hello world'));
124124
}
125125

126+
public function testEmitsCustomEventWhenInputEmitsCustomEvent()
127+
{
128+
$input = new ThroughStream();
129+
$stream = new ReadableUnwrapStream(Promise\resolve($input), 'stderr');
130+
131+
$stream->on('data', $this->expectCallableNever());
132+
$stream->on('stderr', $this->expectCallableOnceWith('hello world'));
133+
$input->emit('stderr', array('hello world'));
134+
}
135+
136+
public function testEmitsDataOnlyOnceWhenCustomEventIsData()
137+
{
138+
$input = new ThroughStream();
139+
$stream = new ReadableUnwrapStream(Promise\resolve($input), 'data');
140+
141+
$stream->on('data', $this->expectCallableOnceWith('hello world'));
142+
$input->emit('data', array('hello world'));
143+
}
144+
145+
public function testEmitsEndOnlyOnceWhenCustomEventIsEnd()
146+
{
147+
$input = new ThroughStream();
148+
$stream = new ReadableUnwrapStream(Promise\resolve($input), 'end');
149+
150+
$stream->on('end', $this->expectCallableOnce());
151+
$input->end();
152+
}
153+
154+
public function testEmitsErrorOnlyOnceWhenCustomEventIsError()
155+
{
156+
$input = new ThroughStream();
157+
$stream = new ReadableUnwrapStream(Promise\resolve($input), 'error');
158+
159+
$stream->on('error', $this->expectCallableOnceWith(new \RuntimeException()));
160+
$input->emit('error', array(new \RuntimeException()));
161+
}
162+
163+
public function testEmitsCloseOnlyOnceWhenCustomEventIsClose()
164+
{
165+
$input = new ThroughStream();
166+
$stream = new ReadableUnwrapStream(Promise\resolve($input), 'close');
167+
168+
$stream->on('close', $this->expectCallableOnce());
169+
$input->close();
170+
}
171+
126172
public function testEmitsErrorAndClosesWhenInputEmitsError()
127173
{
128174
$input = new ThroughStream();

0 commit comments

Comments
 (0)