Skip to content
This repository was archived by the owner on Oct 26, 2019. It is now read-only.
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
26 changes: 26 additions & 0 deletions src/Client/AmpArtaxStreamEndpoint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Docker\Client;

use Amp\Artax\Response;
use Amp\CancellationTokenSource;
use Amp\Promise;
use Jane\OpenApiRuntime\Client\Client;
use Symfony\Component\Serializer\SerializerInterface;

interface AmpArtaxStreamEndpoint
{
/**
* Parse and transform an Artax InputStream chunk into a different object.
*
* Implementations may vary depending the status code of the response and the fetch mode used.
*/
public function parseArtaxStreamResponse(
Response $response,
SerializerInterface $serializer,
CancellationTokenSource $cancellationTokenSource,
string $fetchMode = Client::FETCH_OBJECT
): Promise;
}
41 changes: 41 additions & 0 deletions src/Client/AmpArtaxStreamEndpointTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Docker\Client;

use Amp\Artax\Response;
use Amp\CancellationTokenSource;
use Amp\Promise;
use Docker\Stream\ArtaxCallbackStream;
use Jane\OpenApiRuntime\Client\Client;
use Jane\OpenApiRuntime\Client\Exception\InvalidFetchModeException;
use Symfony\Component\Serializer\SerializerInterface;
use function Amp\call;

trait AmpArtaxStreamEndpointTrait
{
abstract protected function transformResponseBody(string $body, int $status, SerializerInterface $serializer);

public function parseArtaxStreamResponse(
Response $response,
SerializerInterface $serializer,
CancellationTokenSource $cancellationTokenSource,
string $fetchMode = Client::FETCH_OBJECT
): Promise {
if (!\in_array($fetchMode, [Client::FETCH_OBJECT, Client::FETCH_RESPONSE], true)) {
throw new InvalidFetchModeException(\sprintf('Fetch mode %s is not supported', $fetchMode));
}

return call(function () use ($response, $serializer, $fetchMode, $cancellationTokenSource) {
$responseTransformer = null;
if (Client::FETCH_OBJECT === $fetchMode) {
$responseTransformer = function ($chunk) use ($response, $serializer) {
return $this->transformResponseBody($chunk, $response->getStatus(), $serializer);
};
}

return new ArtaxCallbackStream($response->getBody(), $cancellationTokenSource, $responseTransformer);
});
}
}
15 changes: 15 additions & 0 deletions src/Client/ProvideAmpArtaxClientOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace ElevenLabs\Docker\Client;

namespace Docker\Client;

interface ProvideAmpArtaxClientOptions
{
/**
* Return a list of options for the Artax client.
*/
public function getAmpArtaxClientOptions(): array;
}
52 changes: 52 additions & 0 deletions src/DockerAsync.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@

namespace Docker;

use Amp\Artax\Request;
use Amp\CancellationTokenSource;
use Amp\Promise;
use Docker\API\ClientAsync;
use Docker\Client\AmpArtaxStreamEndpoint;
use Docker\Client\ProvideAmpArtaxClientOptions;
use Docker\Endpoint\SystemEvents;
use Jane\OpenApiRuntime\Client\AmpArtaxEndpoint;
use function Amp\call;

/**
* Docker\Docker.
Expand All @@ -19,4 +27,48 @@ public static function create($httpClient = null)

return parent::create($httpClient);
}

/**
* {@inheritdoc}
*/
public function systemEvents(array $queryParameters = [], string $fetch = self::FETCH_OBJECT): Promise
{
return $this->executeArtaxEndpoint(new SystemEvents($queryParameters), $fetch);
}

/**
* {@inheritdoc}
*/
public function executeArtaxEndpoint(AmpArtaxEndpoint $endpoint, string $fetch = self::FETCH_OBJECT): Promise
{
return call(function () use ($endpoint, $fetch) {
[$bodyHeaders, $body] = $endpoint->getBody($this->serializer);
$queryString = $endpoint->getQueryString();
$uri = '' !== $queryString ? $endpoint->getUri().'?'.$queryString : $endpoint->getUri();
$request = new Request($uri, $endpoint->getMethod());
$request = $request->withBody($body);
$request = $request->withHeaders($endpoint->getHeaders($bodyHeaders));
$options = [];
if ($endpoint instanceof ProvideAmpArtaxClientOptions) {
$options = $endpoint->getAmpArtaxClientOptions();
}

if ($endpoint instanceof AmpArtaxStreamEndpoint) {
$cancellationTokenSource = new CancellationTokenSource();

return $endpoint->parseArtaxStreamResponse(
yield $this->httpClient->request($request, $options, $cancellationTokenSource->getToken()),
$this->serializer,
$cancellationTokenSource,
$fetch
);
}

return $endpoint->parseArtaxResponse(
yield $this->httpClient->request($request, $options),
$this->serializer,
$fetch
);
});
}
}
13 changes: 12 additions & 1 deletion src/Endpoint/SystemEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,26 @@

namespace Docker\Endpoint;

use Amp\Artax\Client as ArtaxClient;
use Docker\API\Endpoint\SystemEvents as BaseEndpoint;
use Docker\Client\AmpArtaxStreamEndpoint;
use Docker\Client\AmpArtaxStreamEndpointTrait;
use Docker\Client\ProvideAmpArtaxClientOptions;
use Docker\Stream\EventStream;
use Jane\OpenApiRuntime\Client\Client;
use Jane\OpenApiRuntime\Client\Exception\InvalidFetchModeException;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Serializer\SerializerInterface;

class SystemEvents extends BaseEndpoint
class SystemEvents extends BaseEndpoint implements ProvideAmpArtaxClientOptions, AmpArtaxStreamEndpoint
{
use AmpArtaxStreamEndpointTrait;

public function getAmpArtaxClientOptions(): array
{
return [ArtaxClient::OP_TRANSFER_TIMEOUT => 0];
}

public function parsePSR7Response(ResponseInterface $response, SerializerInterface $serializer, string $fetchMode = Client::FETCH_OBJECT)
{
if (Client::FETCH_OBJECT === $fetchMode) {
Expand Down
78 changes: 78 additions & 0 deletions src/Stream/ArtaxCallbackStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace Docker\Stream;

use Amp\ByteStream\InputStream;
use Amp\CancellationTokenSource;
use Amp\Promise;
use function Amp\call;

class ArtaxCallbackStream
{
private $stream;
private $onNewFrameCallables = [];
private $chunkTransformer;
private $cancellationTokenSource;

public function __construct(
InputStream $stream,
CancellationTokenSource $cancellationTokenSource,
?callable $chunkTransformer
) {
$this->stream = $stream;
$this->cancellationTokenSource = $cancellationTokenSource;
$this->chunkTransformer = $chunkTransformer;
}

/**
* Called when there is a new frame from the stream.
*
* @param callable $onNewFrame
*/
public function onFrame(callable $onNewFrame): void
{
$this->onNewFrameCallables[] = $onNewFrame;
}

/**
* Consume stream chunks.
*
* @return Promise
*/
public function listen(): Promise
{
return call(function () {
while (null !== ($chunk = yield $this->stream->read())) {
foreach ($this->onNewFrameCallables as $newFrameCallable) {
$newFrameCallable($this->transformChunk($chunk));
}
}
});
}

/**
* Stop consuming stream chunks.
*/
public function cancel(): void
{
$this->cancellationTokenSource->cancel();
}

/**
* Transform stream chunks if required.
*
* @param string $chunk
*
* @return mixed The raw chunk or the transformed chunk
*/
private function transformChunk(string $chunk)
{
if (null === $this->chunkTransformer) {
return $chunk;
}

return \call_user_func($this->chunkTransformer, $chunk);
}
}
44 changes: 44 additions & 0 deletions tests/DockerAsyncTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

use Amp\Loop;
use Docker\API\Model\ContainersCreatePostBody;
use Docker\API\Model\EventsGetResponse200;
use Docker\DockerAsync;
use Docker\Stream\ArtaxCallbackStream;

class DockerAsyncTest extends \PHPUnit\Framework\TestCase
{
Expand Down Expand Up @@ -39,4 +41,46 @@ public function testAsync(): void
$this->assertSame($containerCreate->getId(), $containerInfo->getId());
});
}

public function testSystemEventsAllowTheConsumptionOfDockerEvents(): void
{
$matchedEvents = [];

Loop::run(function () use (&$matchedEvents) {
$docker = DockerAsync::create();

/** @var ArtaxCallbackStream $events */
$events = yield $docker->systemEvents([
'filters' => \json_encode(
[
'type' => ['container'],
'action' => ['create'],
]
),
]);
$events->onFrame(function ($event) use (&$matchedEvents): void {
if (\is_object($event)
&& $event instanceof EventsGetResponse200
&& 'create' === $event->getAction()
&& 'container' === $event->getType()
) {
$matchedEvents[] = $event;
}
});

$events->listen();

$containerConfig = new ContainersCreatePostBody();
$containerConfig->setImage('busybox:latest');
$containerConfig->setCmd(['echo', '-n', 'output']);

yield $docker->containerCreate($containerConfig);

Loop::delay(1000, function (): void {
Loop::stop();
});
});

$this->assertCount(1, $matchedEvents);
}
}