diff --git a/README.md b/README.md index 42df5d3..02660c2 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,7 @@ The following API endpoints take advantage of [JSON streaming](https://en.wikipe ```php $client->imageCreate(); $client->imagePush(); +$client->events(); ``` What this means is that these endpoints actually emit any number of progress @@ -271,6 +272,7 @@ a [`Stream`](https://github.com/reactphp/stream) instance instead: ```php $stream = $client->imageCreateStream(); $stream = $client->imagePushStream(); +$stream = $client->eventsStream(); ``` The resulting stream will emit the following events: diff --git a/examples/events.php b/examples/events.php new file mode 100644 index 0000000..8750fb7 --- /dev/null +++ b/examples/events.php @@ -0,0 +1,31 @@ +createClient(); + +// get a list of all events that happened up until this point +// expect this list to be limited to the last 64 (or so) events +// $events = $client->events(0, microtime(true)); + +// get a list of all events that happened in the last 10 seconds +// $events = $client->events(microtime(true) - 10, microtime(true)); + +// stream all events for 10 seconds +$stream = $client->eventsStream(null, microtime(true) + 10.0); + +$stream->on('progress', function ($event) { + echo json_encode($event) . PHP_EOL; +}); + +$stream->on('error', 'printf'); + +$loop->run(); diff --git a/src/Client.php b/src/Client.php index 8c44da2..692b2ca 100644 --- a/src/Client.php +++ b/src/Client.php @@ -93,6 +93,89 @@ public function version() return $this->browser->get('/version')->then(array($this->parser, 'expectJson')); } + /** + * Get container events from docker + * + * This is a JSON streaming API endpoint that resolves with an array of all + * individual progress events. + * + * If you need to access the individual progress events as they happen, you + * should consider using `eventsStream()` instead. + * + * Note that this method will buffer all events until the stream closes. + * This means that you SHOULD pass a timestamp for `$until` so that this + * method only polls the given time interval and then resolves. + * + * The optional `$filters` parameter can be used to only get events for + * certain event types, images and/or containers etc. like this: + * + * $filters = array( + * 'image' => array('ubuntu', 'busybox'), + * 'event' => array('create') + * ); + * + * + * @param float|null $since timestamp used for polling + * @param float|null $until timestamp used for polling + * @param array $filters (optional) filters to apply (requires API v1.16+ / Docker v1.4+) + * @return PromiseInterface Promise array of event objects + * @link https://docs.docker.com/reference/api/docker_remote_api_v1.15/#monitor-dockers-events + * @uses self::eventsStream() + * @see self::eventsStream() + */ + public function events($since = null, $until = null, $filters = array()) + { + return $this->streamingParser->deferredStream( + $this->eventsStream($since, $until, $filters), + 'progress' + ); + } + + /** + * Get container events from docker + * + * This is a JSON streaming API endpoint that returns a stream instance. + * + * The resulting stream will emit the following events: + * - progress: for *each* element in the update stream + * - error: once if an error occurs, will close() stream then + * - close: once the stream ends (either finished or after "error") + * + * Please note that the resulting stream does not emit any "data" events, so + * you will not be able to pipe() its events into another `WritableStream`. + * + * The optional `$filters` parameter can be used to only get events for + * certain event types, images and/or containers etc. like this: + * + * $filters = array( + * 'image' => array('ubuntu', 'busybox'), + * 'event' => array('create') + * ); + * + * + * @param float|null $since timestamp used for polling + * @param float|null $until timestamp used for polling + * @param array $filters (optional) filters to apply (requires API v1.16+ / Docker v1.4+) + * @return ReadableStreamInterface stream of event objects + * @link https://docs.docker.com/reference/api/docker_remote_api_v1.15/#monitor-dockers-events + * @see self::events() + */ + public function eventsStream($since = null, $until = null, $filters = array()) + { + return $this->streamingParser->parseJsonStream( + $this->browser->withOptions(array('streaming' => true))->get( + $this->uri->expand( + '/events{?since,until,filters}', + array( + 'since' => $since, + 'until' => $until, + 'filters' => $filters ? json_encode($filters) : null + ) + ) + ) + ); + } + /** * List containers * diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 2356da7..d0d53f1 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -37,6 +37,30 @@ public function testPing() $this->expectPromiseResolveWith($body, $this->client->ping()); } + public function testEvents() + { + $json = array(); + $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + + $this->expectRequest('GET', '/events', $this->createResponseJsonStream($json)); + $this->streamingParser->expects($this->once())->method('parseJsonStream')->will($this->returnValue($stream)); + $this->streamingParser->expects($this->once())->method('deferredStream')->with($this->equalTo($stream), $this->equalTo('progress'))->will($this->returnPromise($json)); + + $this->expectPromiseResolveWith($json, $this->client->events()); + } + + public function testEventsArgs() + { + $json = array(); + $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + + $this->expectRequest('GET', '/events?since=10&until=20&filters=%7B%22image%22%3A%5B%22busybox%22%2C%22ubuntu%22%5D%7D', $this->createResponseJsonStream($json)); + $this->streamingParser->expects($this->once())->method('parseJsonStream')->will($this->returnValue($stream)); + $this->streamingParser->expects($this->once())->method('deferredStream')->with($this->equalTo($stream), $this->equalTo('progress'))->will($this->returnPromise($json)); + + $this->expectPromiseResolveWith($json, $this->client->events(10, 20, array('image' => array('busybox', 'ubuntu')))); + } + public function testContainerCreate() { $json = array(); diff --git a/tests/FunctionalClientTest.php b/tests/FunctionalClientTest.php index f3092e7..6111d38 100644 --- a/tests/FunctionalClientTest.php +++ b/tests/FunctionalClientTest.php @@ -46,6 +46,8 @@ public function testCreateStartAndRemoveContainer() $this->assertNotNull($container['Id']); $this->assertNull($container['Warnings']); + $start = microtime(true); + $promise = $this->client->containerStart($container['Id']); $ret = Block\await($promise, $this->loop); @@ -55,6 +57,19 @@ public function testCreateStartAndRemoveContainer() $ret = Block\await($promise, $this->loop); $this->assertEquals('', $ret); + + $end = microtime(true); + + // get all events between starting and removing for this container + $promise = $this->client->events($start, $end, array('container' => array($container['Id']))); + $ret = Block\await($promise, $this->loop); + + // expects "start", "kill", "die", "destroy" events + $this->assertEquals(4, count($ret)); + $this->assertEquals('start', $ret[0]['status']); + $this->assertEquals('kill', $ret[1]['status']); + $this->assertEquals('die', $ret[2]['status']); + $this->assertEquals('destroy', $ret[3]['status']); } /**