diff --git a/CHANGELOG.md b/CHANGELOG.md index 3398fd8a..b3b5716d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,13 @@ -# Changelog +# Changelog + +## 2.0 + + - [BC Break] All endpoints have new names and potentially new parameters + - Add async (with amp artax) support + - It now uses the official swagger specification of docker + - Allow to use from 1.25 to 1.36 api version of Docker + - Add support for more keywords in ContextBuilder + - Lot of bug fixes ## 1.24.0 - 08/08/2014 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4360d0c3..6be589b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ please be as precise as possible. Here is a little list of required information: - Precise description of the bug - Details of your environment (for example: OS, PHP version, installed extensions) - - Backtrace which might help identifing the bug + - Backtrace which might help identifying the bug ## Feature requests @@ -75,7 +75,7 @@ endpoint or requested / returned object, you will need to update the `docker-swa There is a bash script at the root of this repository `generate.sh` which helps launching the command to generate files according to the specification. -When changing the specification don't hesite to do 2 commits for better reading: +When changing the specification don't hesitate to do 2 commits for better reading: * One with only changes to the specification * One with changes on the generated code diff --git a/README.md b/README.md index 94092f39..27464be6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +# No longer maintained + +I'm backing off maintaining this library due to a lack of motivation, time and usage of docker, contact me on twitter https://twitter.com/joelwurtz if you wish to take over this repository (or just do a fork). + Docker PHP ========== diff --git a/docs/async.md b/docs/async.md index 36d7cb38..2411e9dc 100644 --- a/docs/async.md +++ b/docs/async.md @@ -1,6 +1,6 @@ # Asynchronous Client -Starting from 2.0 Docker-PHP propose an Asynchronous PHP Client using [Amp](https://amphp.org/) and +Starting from 2.0, Docker-PHP proposes an Asynchronous PHP Client using [Amp](https://amphp.org/) and [Artax](https://amphp.org/artax/). ## Installation diff --git a/docs/basic.md b/docs/basic.md index 2edaeb98..52a7f200 100644 --- a/docs/basic.md +++ b/docs/basic.md @@ -1,7 +1,7 @@ # Basic Usage -`Docker\Docker` API Client offers all endpoints available for your version of Docker. Each endpoint as a strong PHPDoc -documentation in its comment, so the best way to know what values to set for an endpoint and what it returns, is to go +`Docker\Docker` API Client offers all endpoints available for your version of Docker. Each endpoint has a strong PHPDoc +documentation in its comment, so the best way to know what values to set for an endpoint and what it returns is to go directly to the endpoint documentation in the code. As an example for listing container you can do: diff --git a/docs/connection.md b/docs/connection.md index 5aaf622e..92c15212 100644 --- a/docs/connection.md +++ b/docs/connection.md @@ -42,7 +42,7 @@ to learn about possible options. ## Custom client -In fact `Docker\Docker` accept any client from [Httplug](http://httplug.io/) (respecting the `Http\Client\HttpClient` interface). +In fact `Docker\Docker` accepts any client from [Httplug](http://httplug.io/) (respecting the `Http\Client\HttpClient` interface). So you can use [React](https://github.com/reactphp/http-client), [Guzzle](http://docs.guzzlephp.org/en/latest/) or any [other adapters / clients](http://docs.php-http.org/en/latest/clients.html). @@ -65,10 +65,10 @@ $docker = Docker::create($adapter); However not all clients fully support Docker daemon features, such as unix socket domain connection, real time streaming, ... That's why it's strongly encouraged to use the [Socket Http Client](http://docs.php-http.org/en/latest/clients/socket-client.html) -which support all the docker daemon features. +which supports all the docker daemon features. Also this client needs to be decorated by [a plugin system](http://docs.php-http.org/en/latest/plugins/index.html). At least 2 plugins are required: * [Content-Length Plugin](http://docs.php-http.org/en/latest/plugins/content-length.html): Which will set correct header `Content-Length` header for the request; - * [Decoder Plugin](http://docs.php-http.org/en/latest/plugins/decoder.html): Which allow to manipulate chunked and/or encoded response + * [Decoder Plugin](http://docs.php-http.org/en/latest/plugins/decoder.html): Which can manipulate a chunked and/or encoded response diff --git a/docs/cookbook/build-image.md b/docs/cookbook/build-image.md index e412361f..71a5d4bc 100644 --- a/docs/cookbook/build-image.md +++ b/docs/cookbook/build-image.md @@ -37,7 +37,7 @@ $buildStream->wait(); ### Docker::FETCH_RESPONSE -The build function will return the raw [PSR7](http://www.php-fig.org/psr/psr-7/) Response. It's up too you handle +The build function will return the raw [PSR7](http://www.php-fig.org/psr/psr-7/) Response. It's up to you to handle decoding and receiving correct output in this case. ## Context diff --git a/docs/cookbook/container-run.md b/docs/cookbook/container-run.md index 9a506598..2ee824bf 100644 --- a/docs/cookbook/container-run.md +++ b/docs/cookbook/container-run.md @@ -58,6 +58,17 @@ seconds) $docker->containerWait('my-container-unique-name'); ``` +## Stopping the container + +Once your container is started you can stop it by using the `containerStop` method. You can use the id of the container or the name: + +```php +$docker->containerStop($containerCreateResult->getId()); +// Or +$docker->containerStop('my-container-unique-name'); +``` + + ## Reading logs in real time Sometimes you will need to read logs in real time for a container. You can use the `containerAttach` method for that. @@ -98,13 +109,12 @@ method need extra configuration: $containerConfig = new ContainersCreatePostBody(); $containerConfig->setImage('busybox:latest'); $containerConfig->setCmd(['echo', 'I am running a command']); -$containerConfig->setNames(['my-container-unique-name']]); // You need to attach stream of the container to docker $containerConfig->setAttachStdin(true); $containerConfig->setAttachStdout(true); $containerConfig->setAttachStderr(true); -$docker->containerCreate($containerConfig); +$docker->containerCreate($containerConfig, ['name' => 'my-container-unique-name']); // You also need to set stream to true to get the logs, and tell which stream you want to attach $attachStream = $docker->containerAttach('my-container-unique-name', [ @@ -198,3 +208,44 @@ $hostConfig->setPortBindings($portMap); $containerConfig->setHostConfig($hostConfig); ``` + +## Executing a command on a running container + +This example shows how you can execute an command on any running container. It is same as `docker exec CONTAINER_ID some_command `. + +```php +setTty(true); +$execConfig->setAttachStdout(true); +$execConfig->setAttachStderr(true); +$execConfig->setCmd(['mkdir', '/tmp/testDir']); + +$execid = $docker->containerExec('android',$execConfig)->getId(); +$execStartConfig = new ExecIdStartPostBody(); +$execStartConfig->setDetach(false); + +// Execute the command +$stream = $docker->execStart($execid,$execStartConfig); + +// To see the output stream of the 'exec' command +$stdoutText = ""; +$stderrText = ""; + +$stream->onStdout(function ($stdout) use (&$stdoutText) { + $stdoutText .= $stdout; +}); + +$stream->onStderr(function ($stderr) use (&$stderrText) { + $stderrText .= $stderr; +}); + +$stream->wait(); +var_dump([ "stdout" => $stdoutText, "stderr" => $stderrText ]) ; +``` diff --git a/docs/installation.md b/docs/installation.md index d37ad678..2cc4fcec 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,11 +1,11 @@ # Installation -The recommended way to install Docker PHP is of course to use [Composer](http://getcomposer.org/): +The recommended way to install Docker PHP is to use [Composer](http://getcomposer.org/): Run `composer require docker-php/docker-php` to add the dependency By default it will use the last API version. However you can specify the API version of docker by setting a specific -version for the ` docker-php/docker-php-api`. +version for the `docker-php/docker-php-api`. To use the 1.29 version you can do the following: @@ -13,9 +13,9 @@ To use the 1.29 version you can do the following: composer require docker-php/docker-php-api:4.1.29.* ``` -Do not use `^4.1.29.0` otherwise you will also depend on the lastest version. First digit of this version number match the -major version of [Jane PHP](https://github.com/janephp/janephp) which is the lib generating the API Client code. +Do not use `^4.1.29.0`; otherwise, you will also depend on the latest version. The first digit of this version number matches the +major version of [Jane PHP](https://github.com/janephp/janephp), which is the lib generating the API Client code. -Also please not that, unfortunately some endpoints of the Docker API may have BC break during minor version. This library may -try to hide those BC break but it will always be a best effort. Feel free to raise an issue or pull request on github when +Note that some endpoints of the Docker API may have BC breaks during minor version updates. This library may +try to hide those BC breaks but it will always be a best effort. Feel free to raise an issue or pull request on github when you encounter one. diff --git a/src/Client/AmpArtaxStreamEndpoint.php b/src/Client/AmpArtaxStreamEndpoint.php new file mode 100644 index 00000000..ec7f3b9a --- /dev/null +++ b/src/Client/AmpArtaxStreamEndpoint.php @@ -0,0 +1,26 @@ +transformResponseBody($chunk, $response->getStatus(), $serializer); + }; + } + + return new ArtaxCallbackStream($response->getBody(), $cancellationTokenSource, $responseTransformer); + }); + } +} diff --git a/src/Client/ProvideAmpArtaxClientOptions.php b/src/Client/ProvideAmpArtaxClientOptions.php new file mode 100644 index 00000000..cd1aa87d --- /dev/null +++ b/src/Client/ProvideAmpArtaxClientOptions.php @@ -0,0 +1,15 @@ +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 + ); + }); + } } diff --git a/src/DockerAsyncClient.php b/src/DockerAsyncClient.php index 2adc0cab..ca440bd7 100644 --- a/src/DockerAsyncClient.php +++ b/src/DockerAsyncClient.php @@ -61,7 +61,7 @@ public static function createFromEnv(): self if (\getenv('DOCKER_TLS_VERIFY') && '1' === \getenv('DOCKER_TLS_VERIFY')) { if (!\getenv('DOCKER_CERT_PATH')) { - throw new \RuntimeException('Connection to docker has been set to use TLS, but no PATH is defined for certificate in DOCKER_CERT_PATH docker environnement variable'); + throw new \RuntimeException('Connection to docker has been set to use TLS, but no PATH is defined for certificate in DOCKER_CERT_PATH docker environment variable'); } $tlsContext = new ClientTlsContext(); diff --git a/src/DockerClientFactory.php b/src/DockerClientFactory.php index 3d108d06..918d8724 100644 --- a/src/DockerClientFactory.php +++ b/src/DockerClientFactory.php @@ -47,7 +47,7 @@ public static function createFromEnv(PluginClientFactory $pluginClientFactory = if (\getenv('DOCKER_TLS_VERIFY') && '1' === \getenv('DOCKER_TLS_VERIFY')) { if (!\getenv('DOCKER_CERT_PATH')) { - throw new \RuntimeException('Connection to docker has been set to use TLS, but no PATH is defined for certificate in DOCKER_CERT_PATH docker environnement variable'); + throw new \RuntimeException('Connection to docker has been set to use TLS, but no PATH is defined for certificate in DOCKER_CERT_PATH docker environment variable'); } $cafile = \getenv('DOCKER_CERT_PATH').DIRECTORY_SEPARATOR.'ca.pem'; diff --git a/src/Endpoint/SystemEvents.php b/src/Endpoint/SystemEvents.php index ade82394..712e67da 100644 --- a/src/Endpoint/SystemEvents.php +++ b/src/Endpoint/SystemEvents.php @@ -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) { diff --git a/src/Stream/ArtaxCallbackStream.php b/src/Stream/ArtaxCallbackStream.php new file mode 100644 index 00000000..f85d5219 --- /dev/null +++ b/src/Stream/ArtaxCallbackStream.php @@ -0,0 +1,78 @@ +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); + } +} diff --git a/tests/DockerAsyncTest.php b/tests/DockerAsyncTest.php index 2fd15108..29b2f3bf 100644 --- a/tests/DockerAsyncTest.php +++ b/tests/DockerAsyncTest.php @@ -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 { @@ -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); + } } diff --git a/tests/DockerClientFactoryTest.php b/tests/DockerClientFactoryTest.php index 32554766..286419d4 100644 --- a/tests/DockerClientFactoryTest.php +++ b/tests/DockerClientFactoryTest.php @@ -22,7 +22,7 @@ public function testStaticConstructor(): void /** * @expectedException \RuntimeException - * @expectedExceptionMessage Connection to docker has been set to use TLS, but no PATH is defined for certificate in DOCKER_CERT_PATH docker environnement variable + * @expectedExceptionMessage Connection to docker has been set to use TLS, but no PATH is defined for certificate in DOCKER_CERT_PATH docker environment variable */ public function testCreateFromEnvWithoutCertPath(): void {