Coder Social home page Coder Social logo

clue / reactphp-docker Goto Github PK

View Code? Open in Web Editor NEW
100.0 10.0 16.0 236 KB

Async, event-driven access to the Docker Engine API, built on top of ReactPHP.

Home Page: https://clue.engineering/2019/introducing-reactphp-docker

License: MIT License

PHP 100.00%

reactphp-docker's Introduction

clue/reactphp-docker

CI status installs on Packagist

Async, event-driven access to the Docker Engine API, built on top of ReactPHP.

Docker is a popular open source platform to run and share applications within isolated, lightweight containers. The Docker Engine API allows you to control and monitor your containers and images. Among others, it can be used to list existing images, download new images, execute arbitrary commands within isolated containers, stop running containers and much more. This lightweight library provides an efficient way to work with the Docker Engine API from within PHP. It enables you to work with its images and containers or use its event-driven model to react to changes and events happening.

  • Async execution of Actions - Send any number of actions (commands) to your Docker daemon in parallel and process their responses as soon as results come in. The Promise-based design provides a sane interface to working with out of order responses.
  • Lightweight, SOLID design - Provides a thin abstraction that is just good enough and does not get in your way. This library is merely a very thin wrapper around the Docker Engine API.
  • Good test coverage - Comes with an automated tests suite and is regularly tested in the real world.

Table of contents

Support us

We invest a lot of time developing, maintaining and updating our awesome open-source projects. You can help us sustain this high-quality of our work by becoming a sponsor on GitHub. Sponsors get numerous benefits in return, see our sponsoring page for details.

Let's take these projects to the next level together! 🚀

Quickstart example

Once installed, you can use the following code to access the Docker API of your local docker daemon:

<?php

require __DIR__ . '/vendor/autoload.php';

$client = new Clue\React\Docker\Client();

$client->imageSearch('clue')->then(function (array $images) {
    var_dump($images);
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

See also the examples.

Usage

Client

The Client is responsible for assembling and sending HTTP requests to the Docker Engine API.

$client = new Clue\React\Docker\Client();

This class takes an optional LoopInterface|null $loop parameter that can be used to pass the event loop instance to use for this object. You can use a null value here in order to use the default loop. This value SHOULD NOT be given unless you're sure you want to explicitly use a given event loop instance.

If your Docker Engine API is not accessible using the default unix:///var/run/docker.sock Unix domain socket path, you may optionally pass an explicit URL like this:

// explicitly use given UNIX socket path
$client = new Clue\React\Docker\Client(null, 'unix:///var/run/docker.sock');

// or connect via TCP/IP to a remote Docker Engine API
$client = new Clue\React\Docker\Client(null, 'http://10.0.0.2:8000/');

Commands

All public methods on the Client resemble the API described in the Docker Engine API documentation like this:

$client->containerList($all, $size);
$client->containerCreate($config, $name);
$client->containerStart($name);
$client->containerKill($name, $signal);
$client->containerRemove($name, $v, $force);

$client->imageList($all);
$client->imageSearch($term);
$client->imageCreate($fromImage, $fromSrc, $repo, $tag, $registry, $registryAuth);

$client->info();
$client->version();

// many, many more…

Listing all available commands is out of scope here, please refer to the Docker Engine API documentation or the class outline.

Each of these commands supports async operation and either resolves with its results or rejects with an Exception. Please see the following section about promises for more details.

Promises

Sending requests is async (non-blocking), so you can actually send multiple requests in parallel. Docker will respond to each request with a response message, the order is not guaranteed. Sending requests uses a Promise-based interface that makes it easy to react to when a command is completed (i.e. either successfully fulfilled or rejected with an error):

$client->version()->then(
    function ($result) {
        var_dump('Result received', $result);
    },
    function (Exception $e) {
        echo 'Error: ' . $e->getMessage() . PHP_EOL;
    }
});

If this looks strange to you, you can also use the more traditional blocking API.

Blocking

As stated above, this library provides you a powerful, async API by default.

You can also integrate this into your traditional, blocking environment by using reactphp/async. This allows you to simply await commands on the client like this:

use function React\Async\await;

$client = new Clue\React\Docker\Client();

$promise = $client->imageInspect('busybox');

try {
    $results = await($promise);
    // results successfully received
} catch (Exception $e) {
    // an error occurred while performing the request
}

Similarly, you can also process multiple commands concurrently and await an array of results:

use function React\Async\await;
use function React\Promise\all;

$promises = array(
    $client->imageInspect('busybox'),
    $client->imageInspect('ubuntu'),
);

$inspections = await(all($promises));

This is made possible thanks to fibers available in PHP 8.1+ and our compatibility API that also works on all supported PHP versions. Please refer to reactphp/async for more details.

Command streaming

The following API endpoints resolve with a buffered string of the command output (STDOUT and/or STDERR):

$client->containerAttach($container);
$client->containerLogs($container);
$client->execStart($exec);

Keep in mind that this means the whole string has to be kept in memory. If you want to access the individual output chunks as they happen or for bigger command outputs, it's usually a better idea to use a streaming approach.

This works for (any number of) commands of arbitrary sizes. The following API endpoints complement the default Promise-based API and return a Stream instance instead:

$stream = $client->containerAttachStream($container);
$stream = $client->containerLogsStream($container);
$stream = $client->execStartStream($exec);

The resulting stream is a well-behaving readable stream that will emit the normal stream events:

$stream = $client->execStartStream($exec, $tty);

$stream->on('data', function ($data) {
    // data will be emitted in multiple chunk
    echo $data;
});

$stream->on('error', function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

$stream->on('close', function () {
    // the stream just ended, this could(?) be a good thing
    echo 'Ended' . PHP_EOL;
});

Note that by default the output of both STDOUT and STDERR will be emitted as normal data events. You can optionally pass a custom event name which will be used to emit STDERR data so that it can be handled separately. Note that the normal streaming primitives likely do not know about this event, so special care may have to be taken. Also note that this option has no effect if you execute with a TTY.

$stream = $client->execStartStream($exec, $tty, 'stderr');

$stream->on('data', function ($data) {
    echo 'STDOUT data: ' . $data;
});

$stream->on('stderr', function ($data) {
    echo 'STDERR data: ' . $data;
});

See also the streaming exec example and the exec benchmark example.

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 hundreds of megabytes and more). See also the TTY mode on the execStart*() call.

Running the provided benchmark example on a range of systems, it suggests that this library can process several gigabytes per second and may in fact outperform the Docker client and seems to be limited only by the Docker Engine implementation. Instead of posting more details here, you're encouraged to re-run the benchmarks yourself and see for yourself. The key takeway here is: PHP is faster than you probably thought.

TAR streaming

The following API endpoints resolve with a string in the TAR file format:

$client->containerExport($container);
$client->containerArchive($container, $path);

Keep in mind that this means the whole string has to be kept in memory. This is easy to get started and works reasonably well for smaller files/containers.

For bigger containers it's usually a better idea to use a streaming approach, where only small chunks have to be kept in memory. This works for (any number of) files of arbitrary sizes. The following API endpoints complement the default Promise-based API and return a Stream instance instead:

$stream = $client->containerExportStream($image);
$stream = $client->containerArchiveStream($container, $path);

Accessing individual files in the TAR file format string or stream is out of scope for this library. Several libraries are available, one that is known to work is clue/reactphp-tar.

See also the archive example and the export example.

JSON streaming

The following API endpoints take advantage of JSON streaming:

$client->imageCreate();
$client->imagePush();
$client->events();

What this means is that these endpoints actually emit any number of progress events (individual JSON objects). At the HTTP level, a common response message could look like this:

HTTP/1.1 200 OK
Content-Type: application/json

{"status":"loading","current":1,"total":10}
{"status":"loading","current":2,"total":10}
…
{"status":"loading","current":10,"total":10}
{"status":"done","total":10}

The user-facing API hides this fact by resolving with an array of all individual progress events once the stream ends:

$client->imageCreate('clue/streamripper')->then(
    function (array $data) {
        // $data is an array of *all* elements in the JSON stream
        var_dump($data);
    },
    function (Exception $e) {
        // an error occurred (possibly after receiving *some* elements)
        echo 'Error: ' . $e->getMessage() . PHP_EOL;
    }
);

Keep in mind that due to resolving with an array of all progress events, this API has to keep all event objects in memory until the Promise resolves. This is easy to get started and usually works reasonably well for the above API endpoints.

If you're dealing with lots of concurrent requests (100+) or if you want to access the individual progress events as they happen, you should consider using a streaming approach instead, where only individual progress event objects have to be kept in memory. The following API endpoints complement the default Promise-based API and return a Stream instance instead:

$stream = $client->imageCreateStream();
$stream = $client->imagePushStream();
$stream = $client->eventsStream();
$stream = $client->containerStatsStream($container);

The resulting stream will emit the following events:

  • data: for each element in the update stream
  • error: once if an error occurs, will close() stream then
    • Will emit a RuntimeException if an individual progress message contains an error message or any other Exception in case of an transport error, like invalid request etc.
  • close: once the stream ends (either finished or after "error")
$stream = $client->imageCreateStream('clue/redis-benchmark');

$stream->on('data', function (array $data) {
    // data will be emitted for *each* complete element in the JSON stream
    echo $data['status'] . PHP_EOL;
});

$stream->on('error', function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

$stream->on('close', function () {
    // the JSON stream just ended, this could(?) be a good thing
    echo 'Ended' . PHP_EOL;
});

See also the pull example and the push example.

Install

The recommended way to install this library is through Composer. New to Composer?

This project follows SemVer. This will install the latest supported version:

composer require clue/docker-react:^1.5

See also the CHANGELOG for details about version upgrades.

This project aims to run on any platform and thus does not require any PHP extensions and supports running on legacy PHP 5.3 through current PHP 8+. It's highly recommended to use the latest supported PHP version for this project.

Tests

To run the test suite, you first need to clone this repo and then install all dependencies through Composer:

composer install

To run the test suite, go to the project root and run:

vendor/bin/phpunit

License

This project is released under the permissive MIT license.

Did you know that I offer custom development services and issuing invoices for sponsorships of releases and for contributions? Contact me (@clue) for details.

reactphp-docker's People

Contributors

clue avatar dinooo13 avatar paulrotmann avatar simonfrings avatar thojou avatar yadaiio avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reactphp-docker's Issues

Support writable streams (process STDIN)

Quite a few of the API endpoints take advantage of readable streaming APIs.

In the future, we should also look into streaming outgoing data even after having received the HTTP response header. This is relevant for attaching to read/write streams, such as writing to the STDIN of attached streams.

How to atomically read a binary file from container?

Given a container ID (let's say "123abd" and file path "/etc/screenshot.jpg"), is there a way to get the content of the file from outside the container?

At the moment it looks like following:

$loop = Factory::create();
$client = new Client($loop, 'http://host.docker.internal:2375');
$payload = null;
$stream = $client->containerArchiveStream($containerId, $filePath);
$stream->on('data', function (string $data) use (&$payload): void {
    $payload = $data;
});
await(buffer($stream), $loop);
return $payload;

What's the proper way to get the complete content of the file before sending it back from a synchronous method?

Reject streaming API endpoints if message indicates an error

Currently, we only respect the HTTP status codes to detect error conditions. This works for most error conditions – however, this breaks for streaming API endpoints such as pulling an image.

Pulling a non-existant image returns a success status code, however its status events eventually resolve with a message stream like this:

update: {"status":"Pulling repository clue\/does-not-exist"}
update: {"errorDetail":{"message":"Error: image clue\/does-not-exist: not found"},"error":"Error: image clue\/does-not-exist: not found"}

Also refs #4

exec multiplex stream

hi.
@clue i want to write some string to execStartStream returned stream but it's not writable stream .
how can i write to this stream?

Parse streaming responses

Some actions (like pulling images, viewing logs, attaching to containers etc.) produce streaming output. We should parse these HTTP streaming responses into individual virtual streams and forward any events on the virtual stream.

Also notice the difference between HTTP/1.0 and HTTP/1.1 requests.

HTTP/1.0 streaming body:

echo -en "POST /images/create?fromImage=clue/redis-benchmark HTTP/1.0\r\n\r\n" | nc -U /var/run/docker.sock
HTTP/1.0 200 OK
Content-Type: application/json
Date: Thu, 27 Nov 2014 09:42:15 GMT

{"status":"Pulling repository clue/redis-benchmark"}
{"status":"Pulling image (latest) from clue/redis-benchmark","progressDetail":{},"id":"478a1142ea76"}{"status":"Pulling image (latest) from clue/redis-benchmark, endpoint: https://registry-1.docker.io/v1/","progressDetail":{},"id":"478a1142ea76"}{"status":"Pulling dependent layers","progressDetail":{},"id":"478a1142ea76"}{"status":"Download complete","progressDetail":{},"id":"511136ea3c5a"}{"status":"Download complete","progressDetail":{},"id":"f10807909bc5"}{"status":"Download complete","progressDetail":{},"id":"f6fab3b798be"}{"status":"Download complete","progressDetail":{},"id":"1e6ac0ffed3b"}{"status":"Download complete","progressDetail":{},"id":"62ff5003ac9a"}{"status":"Download complete","progressDetail":{},"id":"c6e4fc6c4a10"}{"status":"Download complete","progressDetail":{},"id":"984fd90de307"}{"status":"Download complete","progressDetail":{},"id":"4784dfba86d0"}{"status":"Download complete","progressDetail":{},"id":"4d4120826ad3"}{"status":"Download complete","progressDetail":{},"id":"b2428b25e452"}{"status":"Download complete","progressDetail":{},"id":"28995deeed39"}{"status":"Download complete","progressDetail":{},"id":"9cd1838bd19c"}{"status":"Download complete","progressDetail":{},"id":"c78781ea905b"}{"status":"Download complete","progressDetail":{},"id":"d7173b5f3fc7"}{"status":"Download complete","progressDetail":{},"id":"6a8a6a35a96b"}{"status":"Download complete","progressDetail":{},"id":"28fdd31ac753"}{"status":"Download complete","progressDetail":{},"id":"3ce54e911389"}{"status":"Download complete","progressDetail":{},"id":"b7261e19024d"}{"status":"Download complete","progressDetail":{},"id":"bb182a1a4f8e"}{"status":"Download complete","progressDetail":{},"id":"cabbf32a5995"}{"status":"Download complete","progressDetail":{},"id":"478a1142ea76"}{"status":"Download complete","progressDetail":{},"id":"478a1142ea76"}{"status":"Status: Image is up to date for clue/redis-benchmark"}
…

HTTP/1.1 streaming body uses chunked encoding:

$ echo -en "POST /images/create?fromImage=clue/redis-benchmark HTTP/1.1\r\n\r\n" | nc -U /var/run/docker.sock    
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 27 Nov 2014 09:43:27 GMT
Transfer-Encoding: chunked

36
{"status":"Pulling repository clue/redis-benchmark"}

65
{"status":"Pulling image (latest) from clue/redis-benchmark","progressDetail":{},"id":"478a1142ea76"}
91
{"status":"Pulling image (latest) from clue/redis-benchmark, endpoint: https://registry-1.docker.io/v1/","progressDetail":{},"id":"478a1142ea
76"}
4d
{"status":"Pulling dependent layers","progressDetail":{},"id":"478a1142ea76"}
46
{"status":"Download complete","progressDetail":{},"id":"511136ea3c5a"}
46
{"status":"Download complete","progressDetail":{},"id":"f10807909bc5"}
46
{"status":"Download complete","progressDetail":{},"id":"f6fab3b798be"}
46
{"status":"Download complete","progressDetail":{},"id":"1e6ac0ffed3b"}
46
{"status":"Download complete","progressDetail":{},"id":"62ff5003ac9a"}
46
…

Improve streaming API

Quite a few of the API endpoints take advantage of a streaming API.

We currently offer limited support for streaming incoming HTTP responses.

In the future, we should also look into streaming outgoing data even after having received the HTTP response header. This is relevant for attaching to read/write streams.

This depends on clue/reactphp-buzz#46.

Support boot2docker

Depends on #2 and possibly requires some work to support TLS.

Uses ENV:

DOCKER_HOST

And for TLS:

DOCKER_TLS_VERIFY
DOCKER_CERT_PATH

Request api from http

If ia use php-cli i get api response, but if i use http over php-fpm pointed to same file i get nothing.

What am i missing?

How to access the docker service that hosts the container with my php code?

My web-app runs in a container, and when I try to get the list of other containers running parallel to it, I get the following error message:

"Unable to connect to unix domain socket \"unix:\/\/\/var\/run\/docker.sock\": No such file or directory",
      "message": "Internal server error",

Is this a permission issue or do I need to specify a different URL. Is this possible at all?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.