diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..d95b72a20 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true + +# JSON files +[*.json] +indent_style = space +indent_size = 4 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..d018c1530 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,5 @@ +# These are supported funding model platforms + +github: flavioheleno +patreon: flavioheleno +custom: "https://www.buymeacoffee.com/flavioheleno" diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..e5fae30f4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + # Maintain dependencies for PHP Packages + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml new file mode 100644 index 000000000..854f94f74 --- /dev/null +++ b/.github/workflows/continuous-integration.yml @@ -0,0 +1,54 @@ +name: "Continuous Integration" + +on: + push: + paths-ignore: + - "doc/**" + pull_request: + paths-ignore: + - "doc/**" + +jobs: + phpunit: + name: PHP ${{ matrix.php-version }} (${{ matrix.dependency-versions }}) + + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental }} + + strategy: + fail-fast: false + matrix: + php-version: + - "8.1" + - "8.2" + - "8.3" + - "8.4" + - "8.5" + dependency-versions: [lowest, highest] + experimental: [false] + + steps: + - name: Repository checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Setup PHP with PECL extension + uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2.35.5 + with: + php-version: ${{ matrix.php-version }} + tools: composer:v2 + coverage: pcov + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Install dependencies + uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # v3.1.1 + with: + dependency-versions: ${{ matrix.dependency-versions }} + composer-options: ${{ matrix.composer-options }} + + - name: Pull the docker image used by the tests. + run: docker pull busybox:latest + + - name: Run PHPUnit test suite + run: composer run-script test-ci diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 000000000..43f70ce66 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,39 @@ +name: "Static Analysis" + +on: + push: + paths-ignore: + - 'doc/**' + - '.github/**' + pull_request: + paths-ignore: + - 'doc/**' + - '.github/**' + +jobs: + phpstan: + name: PHPStan + + runs-on: ubuntu-latest + + steps: + - name: Repository checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Setup PHP with PECL extension + uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2.35.5 + with: + php-version: 8.1 + tools: composer:v2 + coverage: none + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Install dependencies + uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # v3.1.1 + with: + dependency-versions: lowest + + - name: Run PHPStan + run: composer run-script phpstan -- --no-progress diff --git a/.gitignore b/.gitignore index 228f5459f..8d49b5514 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ vendor/ bin/ composer.lock .vagrant -/.php_cs.cache +/.php-cs-fixer.cache +/.phpunit.result.cache diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 000000000..3bde3b9fc --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,47 @@ +in([ + __DIR__.'/src', + __DIR__.'/tests', + ]) + ->notPath('/fixtures/') +; + +$config = new \PhpCsFixer\Config(); +return $config + ->setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + '@PHP56Migration:risky' => true, + '@PHP70Migration' => true, + '@PHP70Migration:risky' => true, + '@PHP71Migration' => true, + '@PHP71Migration:risky' => true, + '@PHP73Migration' => true, + '@PHP74Migration' => true, + '@PHP74Migration:risky' => true, + '@PHP80Migration' => true, + '@PHP80Migration:risky' => true, + '@PHPUnit75Migration:risky' => true, + '@PHPUnit84Migration:risky' => true, + 'array_syntax' => [ + 'syntax' => 'short' + ], + 'combine_consecutive_unsets' => true, + 'declare_strict_types' => true, + 'linebreak_after_opening_tag' => true, + 'modernize_types_casting' => true, + 'native_function_invocation' => true, + 'no_php4_constructor' => true, + 'ordered_imports' => true, + 'php_unit_strict' => true, + 'phpdoc_order' => true, + 'strict_comparison' => true, + 'strict_param' => true, + ]) + ->setRiskyAllowed(true) + ->setIndent(' ') + ->setLineEnding("\n") + ->setFinder($finder) +; diff --git a/.php_cs b/.php_cs deleted file mode 100644 index 3eaf3dae5..000000000 --- a/.php_cs +++ /dev/null @@ -1,38 +0,0 @@ -in([ - __DIR__.'/src', - __DIR__.'/tests', - ]) - ->notPath('/fixtures/') -; - -return \PhpCsFixer\Config::create() - ->setRules([ - '@Symfony' => true, - '@Symfony:risky' => true, - '@PHP56Migration' => true, - '@PHP56Migration:risky' => true, - '@PHP70Migration' => true, - '@PHP70Migration:risky' => true, - '@PHP71Migration' => true, - '@PHP71Migration:risky' => true, - 'array_syntax' => [ - 'syntax' => 'short' - ], - 'combine_consecutive_unsets' => true, - 'declare_strict_types' => true, - 'linebreak_after_opening_tag' => true, - 'modernize_types_casting' => true, - 'native_function_invocation' => true, - 'no_php4_constructor' => true, - 'ordered_imports' => true, - 'php_unit_strict' => true, - 'phpdoc_order' => true, - 'strict_comparison' => true, - 'strict_param' => true, - ]) - ->setRiskyAllowed(true) - ->setFinder($finder) -; diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 60d211bf7..000000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,11 +0,0 @@ -filter: - paths: [src/*] -checks: - php: - code_rating: true - duplication: true -tools: - external_code_coverage: true - php_code_sniffer: - config: - standard: "PSR2" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 41708d6f6..000000000 --- a/.travis.yml +++ /dev/null @@ -1,68 +0,0 @@ -language: php -sudo: required - -services: - - docker - -cache: - directories: - - $HOME/.composer/cache - -php: 7.2 - -env: - global: - - TEST_COMMAND="composer test" - matrix: - - DOCKER_API_VERSION=1.36 DOCKER_VERSION=18.02.0~ce-0~ubuntu - - DOCKER_API_VERSION=1.36 DOCKER_VERSION=18.01.0~ce-0~ubuntu - - DOCKER_API_VERSION=1.35 DOCKER_VERSION=17.12.0~ce-0~ubuntu - - DOCKER_API_VERSION=1.34 DOCKER_VERSION=17.11.0~ce-0~ubuntu - - DOCKER_API_VERSION=1.33 DOCKER_VERSION=17.10.0~ce-0~ubuntu - - DOCKER_API_VERSION=1.32 DOCKER_VERSION=17.09.0~ce-0~ubuntu - - DOCKER_API_VERSION=1.31 DOCKER_VERSION=17.07.0~ce-0~ubuntu - - DOCKER_API_VERSION=1.30 DOCKER_VERSION=17.06.2~ce-0~ubuntu - - DOCKER_API_VERSION=1.29 DOCKER_VERSION=17.05.0~ce-0~ubuntu-trusty - - DOCKER_API_VERSION=1.28 DOCKER_VERSION=17.04.0~ce-0~ubuntu-trusty - - DOCKER_API_VERSION=1.27 DOCKER_VERSION=17.03.2~ce-0~ubuntu-trusty - - DOCKER_API_VERSION=1.26 DOCKER_VERSION=17.03.2~ce-0~ubuntu-trusty - - DOCKER_API_VERSION=1.25 DOCKER_VERSION=17.03.2~ce-0~ubuntu-trusty - -matrix: - allow_failures: - - php: hhvm - fast_finish: true - include: - - php: 7.1 - sudo: required - services: - - docker - env: DOCKER_API_VERSION=1.25 DOCKER_VERSION=17.03.2~ce-0~ubuntu-trusty COMPOSER_FLAGS="--prefer-stable --prefer-lowest" COVERAGE=true TEST_COMMAND="composer test-ci" - - php: 7.1 - env: DOCKER_API_VERSION=1.36 DOCKER_VERSION=18.02.0~ce-0~ubuntu TEST_COMMAND="composer lint" - - php: 7.2 - sudo: required - services: - - docker - env: DOCKER_API_VERSION=1.36 DOCKER_VERSION=18.02.0~ce-0~ubuntu - -before_install: - - sudo apt-get update - - sudo apt-cache madison docker-ce - - sudo apt-get -o Dpkg::Options::="--force-confnew" install -y --force-yes docker-ce=${DOCKER_VERSION} - - travis_retry composer self-update - -install: - - travis_retry composer install - - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction - - travis_retry composer require docker-php/docker-php-api:4.${DOCKER_API_VERSION}.* - -script: - - $TEST_COMMAND - -after_success: - - if [[ "$COVERAGE" = true ]]; then wget https://scrutinizer-ci.com/ocular.phar; fi - - if [[ "$COVERAGE" = true ]]; then php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml; fi - -after_script: - - sudo cat /var/log/upstart/docker.log diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6be589b9f..d25bfef16 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,9 +67,9 @@ $ git pull --rebase upstream master $ git push -f origin feature-or-bug-fix-description ``` -## Internal +## Internal -This library consist, for the most part, of auto generated code where the reference is an [Open API Specification (Swagger v2)](https://openapis.org/). In order to modify API +This library consist, for the most part, of auto generated code where the reference is an [Open API Specification (Swagger v2)](https://openapis.org/). In order to modify API endpoint or requested / returned object, you will need to update the `docker-swagger.json` file instead of files in the `generated` directory. There is a bash script at the root of this repository `generate.sh` which helps launching the command to generate files according to the diff --git a/README.md b/README.md index 27464be66..51495e03b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,4 @@ -# 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 -========== +# Docker PHP **Docker PHP** (for lack of a better name) is a [Docker](http://docker.com/) client written in PHP. This library aim to reach 100% API support of the Docker Engine. @@ -11,40 +6,32 @@ This library aim to reach 100% API support of the Docker Engine. The test suite currently passes against Docker Remote API v1.25 to v1.36. [![Documentation Status](https://readthedocs.org/projects/docker-php/badge/?version=latest)](http://docker-php.readthedocs.org/en/latest/) -[![Latest Version](https://img.shields.io/github/release/docker-php/docker-php.svg?style=flat-square)](https://github.com/docker-php/docker-php/releases) +[![Latest Version](https://img.shields.io/github/release/beluga-php/docker-php.svg?style=flat-square)](https://github.com/beluga-php/docker-php/releases) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) -[![Build Status](https://img.shields.io/travis/docker-php/docker-php.svg?branch=master&style=flat-square)](https://travis-ci.org/docker-php/docker-php) -[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/docker-php/docker-php.svg?style=flat-square)](https://scrutinizer-ci.com/g/docker-php/docker-php) -[![Quality Score](https://img.shields.io/scrutinizer/g/docker-php/docker-php.svg?style=flat-square)](https://scrutinizer-ci.com/g/docker-php/docker-php) -[![Total Downloads](https://img.shields.io/packagist/dt/docker-php/docker-php.svg?style=flat-square)](https://packagist.org/packages/docker-php/docker-php) -[![#docker-php on Slack](http://slack.httplug.io/badge.svg)](http://slack.httplug.io) +[![Total Downloads](https://img.shields.io/packagist/dt/beluga-php/docker-php.svg?style=flat-square)](https://packagist.org/packages/beluga-php/docker-php) -Installation ------------- +## Installation The recommended way to install Docker PHP is of course to use [Composer](http://getcomposer.org/): ```bash -composer require docker-php/docker-php +composer require beluga-php/docker-php ``` -Docker API Version ------------------- +## Docker API Version -By default it will use the last version of docker api available, if you want to fix a version (like 1.25) you can add this +By default it will use the last version of docker api available, if you want to fix a version (like 1.41) you can add this requirement to composer: ```bash -composer require "docker-php/docker-php-api:4.1.25.*" +composer require "beluga-php/docker-php-api:6.1.41.*" ``` -Usage ------ +## Usage See [the documentation](http://docker-php.readthedocs.org/en/latest/). -Unit Tests ----------- +## Unit Tests Setup the test suite using [Composer](http://getcomposer.org/) if not already done: @@ -58,17 +45,16 @@ Run it using [PHPUnit](http://phpunit.de/): $ composer test ``` -Contributing ------------- +## Contributing Please see [CONTRIBUTING](CONTRIBUTING.md) for details. -Credits -------- +## Credits This README heavily inspired by [willdurand/Negotiation](https://github.com/willdurand/Negotiation) by @willdurand. This guy is pretty awesome. -License -------- +This library is a fork of the original [docker-php/docker-php](https://github.com/docker-php/docker-php), created by [Geoffrey Bachelet](https://github.com/ubermuda) and [Joel Wurtz](https://github.com/joelwurtz). + +## License The MIT License (MIT). Please see [License File](LICENSE) for more information. diff --git a/composer.json b/composer.json index 64019814f..eca91be79 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,42 @@ { - "name": "docker-php/docker-php", + "name": "beluga-php/docker-php", + "description": "A Docker PHP client", "license": "MIT", "type": "library", - "description": "A Docker PHP client", + "funding": [ + { + "type": "github", + "url": "https://github.com/flavioheleno" + } + ], + "require": { + "php": ">=8.1", + "beluga-php/docker-php-api": "7.1.45.*", + "nyholm/psr7": "^1.8", + "php-http/client-common": "^2.7", + "php-http/discovery": "^1.19", + "php-http/socket-client": "^2.3", + "psr/http-message": "^2.0", + "symfony/filesystem": "^6.3 || ^7.0 || ^8.0", + "symfony/process": "^6.3 || ^7.0 || ^8.0", + "symfony/serializer": "^6.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.42", + "friendsofphp/php-cs-fixer": "^3.8", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5.46", + "psy/psysh": "^0.12.8", + "roave/security-advisories": "dev-latest" + }, + "conflict": { + "docker-php/docker-php": "*", + "nikic/php-parser": "<4.13", + "php-http/message": "<1.15" + }, + "minimum-stability": "dev", + "prefer-stable": true, "autoload": { "psr-4": { "Docker\\": "src/" @@ -13,41 +47,44 @@ "Docker\\Tests\\": "tests/" } }, - "require": { - "php": ">=7.1", - "docker-php/docker-php-api": "4.1.*", - "guzzlehttp/psr7": "^1.2", - "php-http/client-common": "^1.6", - "php-http/socket-client": "^1.3", - "php-http/message": "^1.0", - "symfony/filesystem": "^2.3 || ^3.0 || ^4.0", - "symfony/process": "^2.3 || ^3.0 || ^4.0" - }, - "suggest": { - "php-http/httplug-bundle": "For integration with Symfony", - "amphp/artax": "To use the async api" - }, - "require-dev": { - "phpunit/phpunit": "^6.0", - "friendsofphp/php-cs-fixer": "2.8.1", - "amphp/artax": "^3.0", - "amphp/socket": "^0.10.5" - }, - "conflict": { - "amphp/socket": "<0.10.5", - "amphp/artax": "<3.0" - }, - "scripts": { - "test": "vendor/bin/phpunit", - "test-ci": "vendor/bin/phpunit --coverage-clover build/coverage.xml", - "lint": "vendor/bin/php-cs-fixer fix --dry-run --verbose --diff", - "lint-fix": "vendor/bin/php-cs-fixer fix --verbose" + "config": { + "allow-plugins": { + "ergebnis/composer-normalize": true, + "php-http/discovery": false + }, + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true }, "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "1.41": "1.41.x-dev", + "1.42": "1.42.x-dev", + "1.43": "1.43.x-dev", + "1.44": "1.44.x-dev", + "dev-master": "1.45.x-dev" } }, - "prefer-stable": true, - "minimum-stability": "dev" + "scripts": { + "console": "vendor/bin/psysh", + "lint": "vendor/bin/parallel-lint --exclude vendor .", + "php-cs-fixer": "vendor/bin/php-cs-fixer fix --dry-run --verbose --diff", + "php-cs-fixer-fix": "vendor/bin/php-cs-fixer fix --verbose", + "phpstan": "vendor/bin/phpstan analyse --level 2 src", + "phpunit": "vendor/bin/phpunit ./tests/ --coverage-html=./report/coverage/ --testdox-html=./report/testdox.html --disallow-test-output --process-isolation", + "test": [ + "@lint", + "@phpunit" + ], + "test-ci": "vendor/bin/phpunit ./tests/ --disallow-test-output --process-isolation", + "test-coverage": "vendor/bin/phpunit ./tests/ --whitelist=./src/ --coverage-clover=clover.xml" + }, + "scripts-descriptions": { + "console": "Runs PsySH Console", + "lint": "Runs complete codebase lint testing", + "phpunit": "Runs library test suite", + "test": "Runs all tests", + "test-ci": "Runs library test suite (for continuous integration)", + "test-coverage": "Runs test-coverage analysis" + } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c40525666..cbc386105 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,27 +1,23 @@ - - - - - - tests/ - - - - - src - - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.4/phpunit.xsd" + backupGlobals="false" + colors="true" + processIsolation="false" + stopOnFailure="false" + bootstrap="vendor/autoload.php" + cacheDirectory=".phpunit.cache" + backupStaticProperties="false"> + + + + tests/ + + + + + src + + diff --git a/src/Client/AmpArtaxStreamEndpoint.php b/src/Client/AmpArtaxStreamEndpoint.php deleted file mode 100644 index ec7f3b9a0..000000000 --- a/src/Client/AmpArtaxStreamEndpoint.php +++ /dev/null @@ -1,26 +0,0 @@ -transformResponseBody($chunk, $response->getStatus(), $serializer); - }; - } - - return new ArtaxCallbackStream($response->getBody(), $cancellationTokenSource, $responseTransformer); - }); - } -} diff --git a/src/Client/ProvideAmpArtaxClientOptions.php b/src/Client/ProvideAmpArtaxClientOptions.php deleted file mode 100644 index cd1aa87db..000000000 --- a/src/Client/ProvideAmpArtaxClientOptions.php +++ /dev/null @@ -1,15 +0,0 @@ -directory = $directory; $this->format = $format; @@ -86,7 +86,7 @@ public function setDirectory($directory): void */ public function getDockerfileContent() { - return \file_get_contents($this->directory.DIRECTORY_SEPARATOR.'Dockerfile'); + return file_get_contents($this->directory.\DIRECTORY_SEPARATOR.'Dockerfile'); } /** @@ -114,7 +114,7 @@ public function read() */ public function toTar() { - $process = new Process('/usr/bin/env tar c .', $this->directory); + $process = new Process(['/usr/bin/env', 'tar', '-c', '.'], $this->directory); $process->run(); if (!$process->isSuccessful()) { @@ -132,7 +132,7 @@ public function toTar() public function toStream() { if (!\is_resource($this->process)) { - $this->process = \proc_open('/usr/bin/env tar c .', [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes, $this->directory); + $this->process = proc_open('/usr/bin/env tar -c .', [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes, $this->directory); $this->stream = $pipes[1]; } @@ -142,11 +142,11 @@ public function toStream() public function __destruct() { if (\is_resource($this->stream)) { - \fclose($this->stream); + fclose($this->stream); } if (\is_resource($this->process)) { - \proc_close($this->process); + proc_close($this->process); } if ($this->cleanup) { diff --git a/src/Context/ContextBuilder.php b/src/Context/ContextBuilder.php index 74a4308bf..cda9629f5 100644 --- a/src/Context/ContextBuilder.php +++ b/src/Context/ContextBuilder.php @@ -39,9 +39,9 @@ class ContextBuilder private $entrypoint; /** - * @param \Symfony\Component\Filesystem\Filesystem + * @param \Symfony\Component\Filesystem\Filesystem $fs */ - public function __construct(Filesystem $fs = null) + public function __construct(?Filesystem $fs = null) { $this->fs = $fs ?: new Filesystem(); $this->format = Context::FORMAT_STREAM; @@ -255,7 +255,7 @@ public function volume($volume) */ public function getContext() { - $directory = \sys_get_temp_dir().'/ctb-'.\microtime(); + $directory = sys_get_temp_dir().'/ctb-'.microtime(); $this->fs->mkdir($directory); $this->write($directory); @@ -276,7 +276,7 @@ private function write($directory): void { $dockerfile = []; // Insert a FROM instruction if the file does not start with one. - if (empty($this->commands) || $this->commands[0]['type'] !== 'FROM') { + if (empty($this->commands) || 'FROM' !== $this->commands[0]['type']) { $dockerfile[] = 'FROM base'; } foreach ($this->commands as $command) { @@ -325,7 +325,7 @@ private function write($directory): void $dockerfile[] = 'CMD '.$this->command; } - $this->fs->dumpFile($directory.DIRECTORY_SEPARATOR.'Dockerfile', \implode(PHP_EOL, $dockerfile)); + $this->fs->dumpFile($directory.\DIRECTORY_SEPARATOR.'Dockerfile', implode(\PHP_EOL, $dockerfile)); } /** @@ -338,12 +338,12 @@ private function write($directory): void */ private function getFile($directory, $content) { - $hash = \md5($content); + $hash = md5($content); if (!\array_key_exists($hash, $this->files)) { - $file = \tempnam($directory, ''); + $file = tempnam($directory, ''); $this->fs->dumpFile($file, $content); - $this->files[$hash] = \basename($file); + $this->files[$hash] = basename($file); } return $this->files[$hash]; @@ -359,14 +359,14 @@ private function getFile($directory, $content) */ private function getFileFromStream($directory, $stream) { - $file = \tempnam($directory, ''); - $target = \fopen($file, 'w'); - if (0 === \stream_copy_to_stream($stream, $target)) { + $file = tempnam($directory, ''); + $target = fopen($file, 'w'); + if (0 === stream_copy_to_stream($stream, $target)) { throw new \RuntimeException('Failed to write stream to file'); } - \fclose($target); + fclose($target); - return \basename($file); + return basename($file); } /** @@ -379,10 +379,10 @@ private function getFileFromStream($directory, $stream) */ private function getFileFromDisk($directory, $source) { - $hash = 'DISK-'.\md5(\realpath($source)); + $hash = 'DISK-'.md5(realpath($source)); if (!\array_key_exists($hash, $this->files)) { // Check if source is a directory or a file. - if (\is_dir($source)) { + if (is_dir($source)) { $this->fs->mirror($source, $directory.'/'.$hash, null, ['copy_on_windows' => true]); } else { $this->fs->copy($source, $directory.'/'.$hash); diff --git a/src/Docker.php b/src/Docker.php index 591acb5f0..9333e7e3c 100644 --- a/src/Docker.php +++ b/src/Docker.php @@ -5,7 +5,10 @@ namespace Docker; use Docker\API\Client; +use Docker\API\Endpoint\SystemInfo; +use Docker\API\Exception\BadRequestException; use Docker\API\Model\AuthConfig; +use Docker\API\Model\ExecIdStartPostBody; use Docker\Endpoint\ContainerAttach; use Docker\Endpoint\ContainerAttachWebsocket; use Docker\Endpoint\ContainerLogs; @@ -23,61 +26,57 @@ class Docker extends Client /** * {@inheritdoc} */ - public function containerAttach(string $id, array $queryParameters = [], string $fetch = self::FETCH_OBJECT) + public function containerAttach(string $id, array $queryParameters = [], string $fetch = self::FETCH_OBJECT, array $accept = []) { - return $this->executePsr7Endpoint(new ContainerAttach($id, $queryParameters), $fetch); + return $this->executeEndpoint(new ContainerAttach($id, $queryParameters, $accept), $fetch); } /** * {@inheritdoc} */ - public function containerAttachWebsocket(string $id, array $queryParameters = [], string $fetch = self::FETCH_OBJECT) + public function containerAttachWebsocket(string $id, array $queryParameters = [], string $fetch = self::FETCH_OBJECT, array $accept = []) { - return $this->executePsr7Endpoint(new ContainerAttachWebsocket($id, $queryParameters), $fetch); + return $this->executeEndpoint(new ContainerAttachWebsocket($id, $queryParameters, $accept), $fetch); } /** * {@inheritdoc} */ - public function containerLogs(string $id, array $queryParameters = [], string $fetch = self::FETCH_OBJECT) + public function containerLogs(string $id, array $queryParameters = [], string $fetch = self::FETCH_OBJECT, array $accept = []) { - return $this->executePsr7Endpoint(new ContainerLogs($id, $queryParameters), $fetch); + return $this->executeEndpoint(new ContainerLogs($id, $queryParameters, $accept), $fetch); } /** * {@inheritdoc} */ - public function execStart(string $id, \Docker\API\Model\ExecIdStartPostBody $execStartConfig, string $fetch = self::FETCH_OBJECT) + public function execStart(string $id, ?ExecIdStartPostBody $requestBody = null, string $fetch = self::FETCH_OBJECT, array $accept = []) { - return $this->executePsr7Endpoint(new ExecStart($id, $execStartConfig), $fetch); + return $this->executeEndpoint(new ExecStart($id, $requestBody, $accept), $fetch); } /** * {@inheritdoc} */ - public function imageBuild($inputStream, array $queryParameters = [], array $headerParameters = [], string $fetch = self::FETCH_OBJECT) + public function imageBuild($requestBody = null, array $queryParameters = [], array $headerParameters = [], string $fetch = self::FETCH_OBJECT) { - return $this->executePsr7Endpoint(new ImageBuild($inputStream, $queryParameters, $headerParameters), $fetch); + return $this->executeEndpoint(new ImageBuild($requestBody, $queryParameters, $headerParameters), $fetch); } /** * {@inheritdoc} */ - public function imageCreate(string $inputImage, array $queryParameters = [], array $headerParameters = [], string $fetch = self::FETCH_OBJECT) + public function imageCreate(?string $requestBody = null, array $queryParameters = [], array $headerParameters = [], string $fetch = self::FETCH_OBJECT) { - return $this->executePsr7Endpoint(new ImageCreate($inputImage, $queryParameters, $headerParameters), $fetch); + return $this->executeEndpoint(new ImageCreate($requestBody, $queryParameters, $headerParameters), $fetch); } - - /** - * {@inheritdoc} - */ - public function imagePush(string $name, array $queryParameters = [], array $headerParameters = [], string $fetch = self::FETCH_OBJECT) + public function imagePush(string $name, array $queryParameters = [], array $headerParameters = [], string $fetch = self::FETCH_OBJECT, array $accept = []) { if (isset($headerParameters['X-Registry-Auth']) && $headerParameters['X-Registry-Auth'] instanceof AuthConfig) { - $headerParameters['X-Registry-Auth'] = \base64_encode($this->serializer->serialize($headerParameters['X-Registry-Auth'], 'json')); + $headerParameters['X-Registry-Auth'] = base64_encode($this->serializer->serialize($headerParameters['X-Registry-Auth'], 'json')); } - return $this->executePsr7Endpoint(new ImagePush($name, $queryParameters, $headerParameters), $fetch); + return $this->executeEndpoint(new ImagePush($name, $queryParameters, $headerParameters, $accept), $fetch); } /** @@ -85,15 +84,35 @@ public function imagePush(string $name, array $queryParameters = [], array $head */ public function systemEvents(array $queryParameters = [], string $fetch = self::FETCH_OBJECT) { - return $this->executePsr7Endpoint(new SystemEvents($queryParameters), $fetch); + return $this->executeEndpoint(new SystemEvents($queryParameters), $fetch); } - public static function create($httpClient = null) + public static function create( + $httpClient = null, + array $additionalPlugins = [], + array $additionalNormalizers = [] + ): self { if (null === $httpClient) { $httpClient = DockerClientFactory::createFromEnv(); } - return parent::create($httpClient); + $client = parent::create($httpClient, $additionalPlugins, $additionalNormalizers); + $testClient = $client->executeRawEndpoint(new SystemInfo())->getBody()->getContents(); + $jsonObj = json_decode($testClient); + + if ($jsonObj !== null) { + if (isset($jsonObj->message)) { + // Check if the client is too new + if (strpos($jsonObj->message, 'client version') !== false && strpos($jsonObj->message, 'is too new') !== false) { + throw new BadRequestException("The client version is not supported by your version of Docker. Message: {$jsonObj->message}"); + } else { + throw new BadRequestException($jsonObj->message); + } + } + } else { + throw new BadRequestException("Failed to decode JSON."); + } + return $client; } } diff --git a/src/DockerAsync.php b/src/DockerAsync.php deleted file mode 100644 index 111b4efc5..000000000 --- a/src/DockerAsync.php +++ /dev/null @@ -1,74 +0,0 @@ -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 deleted file mode 100644 index ca440bd7b..000000000 --- a/src/DockerAsyncClient.php +++ /dev/null @@ -1,87 +0,0 @@ -resolveOptions($options); - $socketPool = new HttpSocketPool(new StaticSocketPool($options['remote_socket'], new BasicSocketPool())); - $this->client = new DefaultClient(null, $socketPool, $options['ssl']); - } - - public function request($uriOrRequest, array $options = [], CancellationToken $cancellation = null): Promise - { - if ($uriOrRequest instanceof Request) { - $uriOrRequest = $uriOrRequest->withUri('http://localhost'.$uriOrRequest->getUri()); - } - - return $this->client->request($uriOrRequest, $options, $cancellation); - } - - protected function resolveOptions(array $options = []): array - { - $resolver = new OptionsResolver(); - - $resolver->setDefaults([ - 'ssl' => null, - ]); - - $resolver->setRequired([ - 'remote_socket', - ]); - - $resolver->setAllowedTypes('ssl', ['null', ClientTlsContext::class]); - - return $resolver->resolve($options); - } - - public static function createFromEnv(): self - { - $options = [ - 'remote_socket' => \getenv('DOCKER_HOST') ? \getenv('DOCKER_HOST') : 'unix:///var/run/docker.sock', - ]; - - 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 environment variable'); - } - - $tlsContext = new ClientTlsContext(); - - $cafile = \getenv('DOCKER_CERT_PATH').DIRECTORY_SEPARATOR.'ca.pem'; - $certfile = \getenv('DOCKER_CERT_PATH').DIRECTORY_SEPARATOR.'cert.pem'; - $keyfile = \getenv('DOCKER_CERT_PATH').DIRECTORY_SEPARATOR.'key.pem'; - - $certificate = new Certificate($certfile, $keyfile); - - $tlsContext = $tlsContext->withCaFile($cafile); - $tlsContext = $tlsContext->withCertificate($certificate); - - if (\getenv('DOCKER_PEER_NAME')) { - $tlsContext = $tlsContext->withPeerName(\getenv('DOCKER_PEER_NAME')); - } - - $options['ssl'] = $tlsContext; - } - - return new static($options); - } -} diff --git a/src/DockerClientFactory.php b/src/DockerClientFactory.php index 918d87240..39b07e890 100644 --- a/src/DockerClientFactory.php +++ b/src/DockerClientFactory.php @@ -4,55 +4,62 @@ namespace Docker; -use GuzzleHttp\Psr7\Uri; use Http\Client\Common\Plugin\AddHostPlugin; +use Http\Client\Common\Plugin\AddPathPlugin; use Http\Client\Common\Plugin\ContentLengthPlugin; use Http\Client\Common\Plugin\DecoderPlugin; +use Http\Client\Common\Plugin\HeaderDefaultsPlugin; +use Http\Client\Common\PluginClient; use Http\Client\Common\PluginClientFactory; -use Http\Client\HttpClient; -use Http\Client\Socket\Client as SocketHttpClient; -use Http\Message\MessageFactory\GuzzleMessageFactory; +use Http\Client\Socket\Client; +use Http\Discovery\Psr17FactoryDiscovery; final class DockerClientFactory { - /** - * ( . - */ - public static function create(array $config = [], PluginClientFactory $pluginClientFactory = null): HttpClient + public static function create(array $config = [], ?PluginClientFactory $pluginClientFactory = null): PluginClient { if (!\array_key_exists('remote_socket', $config)) { $config['remote_socket'] = 'unix:///var/run/docker.sock'; } - $messageFactory = new GuzzleMessageFactory(); - $socketClient = new SocketHttpClient($messageFactory, $config); - $host = \preg_match('/unix:\/\//', $config['remote_socket']) ? 'http://localhost' : $config['remote_socket']; + $socketClient = new Client($config); - $pluginClientFactory = $pluginClientFactory ?? new PluginClientFactory(); + $uriFactory = Psr17FactoryDiscovery::findUriFactory(); + $host = preg_match('/unix:\/\//', $config['remote_socket']) ? 'http://localhost' : $config['remote_socket']; - return $pluginClientFactory->createClient($socketClient, [ - new ContentLengthPlugin(), - new DecoderPlugin(), - new AddHostPlugin(new Uri($host)), - ], [ - 'client_name' => 'docker-client', - ]); + $pluginClientFactory ??= new PluginClientFactory(); + + return $pluginClientFactory->createClient( + $socketClient, + [ + new ContentLengthPlugin(), + new DecoderPlugin(), + new AddPathPlugin($uriFactory->createUri('/v1.45')), + new AddHostPlugin($uriFactory->createUri($host)), + new HeaderDefaultsPlugin([ + 'host' => parse_url($host, \PHP_URL_HOST), + ]), + ], + [ + 'client_name' => 'docker-client', + ] + ); } - public static function createFromEnv(PluginClientFactory $pluginClientFactory = null): HttpClient + public static function createFromEnv(?PluginClientFactory $pluginClientFactory = null): PluginClient { $options = [ - 'remote_socket' => \getenv('DOCKER_HOST') ? \getenv('DOCKER_HOST') : 'unix:///var/run/docker.sock', + 'remote_socket' => getenv('DOCKER_HOST') ? getenv('DOCKER_HOST') : 'unix:///var/run/docker.sock', ]; - if (\getenv('DOCKER_TLS_VERIFY') && '1' === \getenv('DOCKER_TLS_VERIFY')) { - if (!\getenv('DOCKER_CERT_PATH')) { + 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 environment variable'); } - $cafile = \getenv('DOCKER_CERT_PATH').DIRECTORY_SEPARATOR.'ca.pem'; - $certfile = \getenv('DOCKER_CERT_PATH').DIRECTORY_SEPARATOR.'cert.pem'; - $keyfile = \getenv('DOCKER_CERT_PATH').DIRECTORY_SEPARATOR.'key.pem'; + $cafile = getenv('DOCKER_CERT_PATH').\DIRECTORY_SEPARATOR.'ca.pem'; + $certfile = getenv('DOCKER_CERT_PATH').\DIRECTORY_SEPARATOR.'cert.pem'; + $keyfile = getenv('DOCKER_CERT_PATH').\DIRECTORY_SEPARATOR.'key.pem'; $stream_context = [ 'cafile' => $cafile, @@ -60,8 +67,8 @@ public static function createFromEnv(PluginClientFactory $pluginClientFactory = 'local_pk' => $keyfile, ]; - if (\getenv('DOCKER_PEER_NAME')) { - $stream_context['peer_name'] = \getenv('DOCKER_PEER_NAME'); + if (getenv('DOCKER_PEER_NAME')) { + $stream_context['peer_name'] = getenv('DOCKER_PEER_NAME'); } $options['ssl'] = true; diff --git a/src/Endpoint/ContainerAttach.php b/src/Endpoint/ContainerAttach.php index a2d180e93..1c776639e 100644 --- a/src/Endpoint/ContainerAttach.php +++ b/src/Endpoint/ContainerAttach.php @@ -6,27 +6,17 @@ use Docker\API\Endpoint\ContainerAttach as BaseEndpoint; use Docker\Stream\DockerRawStream; -use Jane\OpenApiRuntime\Client\Client; -use Jane\OpenApiRuntime\Client\Exception\InvalidFetchModeException; use Psr\Http\Message\ResponseInterface; use Symfony\Component\Serializer\SerializerInterface; class ContainerAttach extends BaseEndpoint { - public function parsePSR7Response(ResponseInterface $response, SerializerInterface $serializer, string $fetchMode = Client::FETCH_OBJECT) + protected function transformResponseBody(ResponseInterface $response, SerializerInterface $serializer, ?string $contentType = null) { - if (Client::FETCH_OBJECT === $fetchMode) { - if (200 === $response->getStatusCode() && DockerRawStream::HEADER === $response->getHeaderLine('Content-Type')) { - return new DockerRawStream($response->getBody()); - } - - return $this->transformResponseBody((string) $response->getBody(), $response->getStatusCode(), $serializer); - } - - if (Client::FETCH_RESPONSE === $fetchMode) { - return $response; + if (200 === $response->getStatusCode() && DockerRawStream::HEADER === $contentType) { + return new DockerRawStream($response->getBody()); } - throw new InvalidFetchModeException(\sprintf('Fetch mode %s is not supported', $fetchMode)); + return parent::transformResponseBody($response, $serializer, $contentType); } } diff --git a/src/Endpoint/ContainerAttachWebsocket.php b/src/Endpoint/ContainerAttachWebsocket.php index fefb3638a..ee6fe0237 100644 --- a/src/Endpoint/ContainerAttachWebsocket.php +++ b/src/Endpoint/ContainerAttachWebsocket.php @@ -6,8 +6,7 @@ use Docker\API\Endpoint\ContainerAttachWebsocket as BaseEndpoint; use Docker\Stream\AttachWebsocketStream; -use Jane\OpenApiRuntime\Client\Client; -use Jane\OpenApiRuntime\Client\Exception\InvalidFetchModeException; +use Docker\Stream\DockerRawStream; use Psr\Http\Message\ResponseInterface; use Symfony\Component\Serializer\SerializerInterface; @@ -15,30 +14,26 @@ class ContainerAttachWebsocket extends BaseEndpoint { public function getExtraHeaders(): array { - return \array_merge(parent::getExtraHeaders(), [ - 'Host' => 'localhost', - 'Origin' => 'php://docker-php', - 'Upgrade' => 'websocket', - 'Connection' => 'Upgrade', - 'Sec-WebSocket-Version' => '13', - 'Sec-WebSocket-Key' => \base64_encode(\uniqid()), - ]); + return array_merge( + parent::getExtraHeaders(), + [ + 'Host' => 'localhost', + 'Origin' => 'php://docker-php', + 'Upgrade' => 'websocket', + 'Connection' => 'Upgrade', + 'Sec-WebSocket-Version' => '13', + 'Sec-WebSocket-Key' => base64_encode(uniqid()), + ] + ); } - public function parsePSR7Response(ResponseInterface $response, SerializerInterface $serializer, string $fetchMode = Client::FETCH_OBJECT) + protected function transformResponseBody(ResponseInterface $response, SerializerInterface $serializer, ?string + $contentType = null) { - if (Client::FETCH_OBJECT === $fetchMode) { - if (101 === $response->getStatusCode()) { - return new AttachWebsocketStream($response->getBody()); - } - - return $this->transformResponseBody((string) $response->getBody(), $response->getStatusCode(), $serializer); - } - - if (Client::FETCH_RESPONSE === $fetchMode) { - return $response; + if (200 === $response->getStatusCode() && DockerRawStream::HEADER === $contentType) { + return new AttachWebsocketStream($response->getBody()); } - throw new InvalidFetchModeException(\sprintf('Fetch mode %s is not supported', $fetchMode)); + return parent::transformResponseBody($response, $serializer, $contentType); } } diff --git a/src/Endpoint/ContainerLogs.php b/src/Endpoint/ContainerLogs.php index 42335752f..996b09c39 100644 --- a/src/Endpoint/ContainerLogs.php +++ b/src/Endpoint/ContainerLogs.php @@ -6,27 +6,17 @@ use Docker\API\Endpoint\ContainerLogs as BaseEndpoint; use Docker\Stream\DockerRawStream; -use Jane\OpenApiRuntime\Client\Client; -use Jane\OpenApiRuntime\Client\Exception\InvalidFetchModeException; use Psr\Http\Message\ResponseInterface; use Symfony\Component\Serializer\SerializerInterface; class ContainerLogs extends BaseEndpoint { - public function parsePSR7Response(ResponseInterface $response, SerializerInterface $serializer, string $fetchMode = Client::FETCH_OBJECT) + protected function transformResponseBody(ResponseInterface $response, SerializerInterface $serializer, ?string $contentType = null) { - if (Client::FETCH_OBJECT === $fetchMode) { - if (200 === $response->getStatusCode()) { - return new DockerRawStream($response->getBody()); - } - - return $this->transformResponseBody((string) $response->getBody(), $response->getStatusCode(), $serializer); - } - - if (Client::FETCH_RESPONSE === $fetchMode) { - return $response; + if (200 === $response->getStatusCode() && DockerRawStream::HEADER === $contentType) { + return new DockerRawStream($response->getBody()); } - throw new InvalidFetchModeException(\sprintf('Fetch mode %s is not supported', $fetchMode)); + return parent::transformResponseBody($response, $serializer, $contentType); } } diff --git a/src/Endpoint/ExecStart.php b/src/Endpoint/ExecStart.php index 48ddd5c8d..e2d8985be 100644 --- a/src/Endpoint/ExecStart.php +++ b/src/Endpoint/ExecStart.php @@ -6,27 +6,17 @@ use Docker\API\Endpoint\ExecStart as BaseEndpoint; use Docker\Stream\DockerRawStream; -use Jane\OpenApiRuntime\Client\Client; -use Jane\OpenApiRuntime\Client\Exception\InvalidFetchModeException; use Psr\Http\Message\ResponseInterface; use Symfony\Component\Serializer\SerializerInterface; class ExecStart extends BaseEndpoint { - public function parsePSR7Response(ResponseInterface $response, SerializerInterface $serializer, string $fetchMode = Client::FETCH_OBJECT) + protected function transformResponseBody(ResponseInterface $response, SerializerInterface $serializer, ?string $contentType = null) { - if (Client::FETCH_OBJECT === $fetchMode) { - if (200 === $response->getStatusCode() && DockerRawStream::HEADER === $response->getHeaderLine('Content-Type')) { - return new DockerRawStream($response->getBody()); - } - - return $this->transformResponseBody((string) $response->getBody(), $response->getStatusCode(), $serializer); - } - - if (Client::FETCH_RESPONSE === $fetchMode) { - return $response; + if (200 === $response->getStatusCode() && DockerRawStream::HEADER === $contentType) { + return new DockerRawStream($response->getBody()); } - throw new InvalidFetchModeException(\sprintf('Fetch mode %s is not supported', $fetchMode)); + return parent::transformResponseBody($response, $serializer, $contentType); } } diff --git a/src/Endpoint/ImageBuild.php b/src/Endpoint/ImageBuild.php index 65d3353fc..49239c4cf 100644 --- a/src/Endpoint/ImageBuild.php +++ b/src/Endpoint/ImageBuild.php @@ -7,38 +7,29 @@ use Docker\API\Endpoint\ImageBuild as BaseEndpoint; use Docker\Stream\BuildStream; use Docker\Stream\TarStream; -use Jane\OpenApiRuntime\Client\Client; -use Jane\OpenApiRuntime\Client\Exception\InvalidFetchModeException; +use Nyholm\Psr7\Stream; use Psr\Http\Message\ResponseInterface; use Symfony\Component\Serializer\SerializerInterface; class ImageBuild extends BaseEndpoint { - public function getBody(\Symfony\Component\Serializer\SerializerInterface $serializer, \Http\Message\StreamFactory $streamFactory = null): array + public function getBody(SerializerInterface $serializer, $streamFactory = null): array { $body = $this->body; if (\is_resource($body)) { - $body = new TarStream($body); + $body = new TarStream(Stream::create($body)); } - return [[], $body]; + return [['Content-Type' => ['application/octet-stream']], $body]; } - public function parsePSR7Response(ResponseInterface $response, SerializerInterface $serializer, string $fetchMode = Client::FETCH_OBJECT) + protected function transformResponseBody(ResponseInterface $response, SerializerInterface $serializer, ?string $contentType = null) { - if (Client::FETCH_OBJECT === $fetchMode) { - if (200 === $response->getStatusCode()) { - return new BuildStream($response->getBody(), $serializer); - } - - return $this->transformResponseBody((string) $response->getBody(), $response->getStatusCode(), $serializer); - } - - if (Client::FETCH_RESPONSE === $fetchMode) { - return $response; + if (200 === $response->getStatusCode()) { + return new BuildStream($response->getBody(), $serializer); } - throw new InvalidFetchModeException(\sprintf('Fetch mode %s is not supported', $fetchMode)); + return parent::transformResponseBody($response, $serializer, $contentType); } } diff --git a/src/Endpoint/ImageCreate.php b/src/Endpoint/ImageCreate.php index 196ba3e5e..3a50da2e9 100644 --- a/src/Endpoint/ImageCreate.php +++ b/src/Endpoint/ImageCreate.php @@ -6,27 +6,17 @@ use Docker\API\Endpoint\ImageCreate as BaseEndpoint; use Docker\Stream\CreateImageStream; -use Jane\OpenApiRuntime\Client\Client; -use Jane\OpenApiRuntime\Client\Exception\InvalidFetchModeException; use Psr\Http\Message\ResponseInterface; use Symfony\Component\Serializer\SerializerInterface; class ImageCreate extends BaseEndpoint { - public function parsePSR7Response(ResponseInterface $response, SerializerInterface $serializer, string $fetchMode = Client::FETCH_OBJECT) + protected function transformResponseBody(ResponseInterface $response, SerializerInterface $serializer, ?string $contentType = null) { - if (Client::FETCH_OBJECT === $fetchMode) { - if (200 === $response->getStatusCode()) { - return new CreateImageStream($response->getBody(), $serializer); - } - - return $this->transformResponseBody((string) $response->getBody(), $response->getStatusCode(), $serializer); - } - - if (Client::FETCH_RESPONSE === $fetchMode) { - return $response; + if (200 === $response->getStatusCode()) { + return new CreateImageStream($response->getBody(), $serializer); } - throw new InvalidFetchModeException(\sprintf('Fetch mode %s is not supported', $fetchMode)); + return parent::transformResponseBody($response, $serializer, $contentType); } } diff --git a/src/Endpoint/ImagePush.php b/src/Endpoint/ImagePush.php index 3d2eabcca..61e165915 100644 --- a/src/Endpoint/ImagePush.php +++ b/src/Endpoint/ImagePush.php @@ -6,8 +6,6 @@ use Docker\API\Endpoint\ImagePush as BaseEndpoint; use Docker\Stream\PushStream; -use Jane\OpenApiRuntime\Client\Client; -use Jane\OpenApiRuntime\Client\Exception\InvalidFetchModeException; use Psr\Http\Message\ResponseInterface; use Symfony\Component\Serializer\SerializerInterface; @@ -15,23 +13,15 @@ class ImagePush extends BaseEndpoint { public function getUri(): string { - return \str_replace(['{name}'], [\urlencode($this->name)], '/images/{name}/push'); + return str_replace(['{name}'], [urlencode($this->name)], '/images/{name}/push'); } - public function parsePSR7Response(ResponseInterface $response, SerializerInterface $serializer, string $fetchMode = Client::FETCH_OBJECT) + protected function transformResponseBody(ResponseInterface $response, SerializerInterface $serializer, ?string $contentType = null) { - if (Client::FETCH_OBJECT === $fetchMode) { - if (200 === $response->getStatusCode()) { - return new PushStream($response->getBody(), $serializer); - } - - return $this->transformResponseBody((string) $response->getBody(), $response->getStatusCode(), $serializer); - } - - if (Client::FETCH_RESPONSE === $fetchMode) { - return $response; + if (200 === $response->getStatusCode()) { + return new PushStream($response->getBody(), $serializer); } - throw new InvalidFetchModeException(\sprintf('Fetch mode %s is not supported', $fetchMode)); + return parent::transformResponseBody($response, $serializer, $contentType); } } diff --git a/src/Endpoint/SystemEvents.php b/src/Endpoint/SystemEvents.php index 712e67da5..3f57e916c 100644 --- a/src/Endpoint/SystemEvents.php +++ b/src/Endpoint/SystemEvents.php @@ -4,40 +4,19 @@ 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 implements ProvideAmpArtaxClientOptions, AmpArtaxStreamEndpoint +class SystemEvents extends BaseEndpoint { - use AmpArtaxStreamEndpointTrait; - - public function getAmpArtaxClientOptions(): array - { - return [ArtaxClient::OP_TRANSFER_TIMEOUT => 0]; - } - - public function parsePSR7Response(ResponseInterface $response, SerializerInterface $serializer, string $fetchMode = Client::FETCH_OBJECT) + protected function transformResponseBody(ResponseInterface $response, SerializerInterface $serializer, ?string $contentType = null) { - if (Client::FETCH_OBJECT === $fetchMode) { - if (200 === $response->getStatusCode()) { - return new EventStream($response->getBody(), $serializer); - } - - return $this->transformResponseBody((string) $response->getBody(), $response->getStatusCode(), $serializer); - } - - if (Client::FETCH_RESPONSE === $fetchMode) { - return $response; + if (200 === $response->getStatusCode()) { + return new EventStream($response->getBody(), $serializer); } - throw new InvalidFetchModeException(\sprintf('Fetch mode %s is not supported', $fetchMode)); + return parent::transformResponseBody($response, $serializer, $contentType); } } diff --git a/src/Stream/ArtaxCallbackStream.php b/src/Stream/ArtaxCallbackStream.php deleted file mode 100644 index f85d5219e..000000000 --- a/src/Stream/ArtaxCallbackStream.php +++ /dev/null @@ -1,78 +0,0 @@ -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/src/Stream/AttachWebsocketStream.php b/src/Stream/AttachWebsocketStream.php index 402e417ca..0394ac2f8 100644 --- a/src/Stream/AttachWebsocketStream.php +++ b/src/Stream/AttachWebsocketStream.php @@ -30,7 +30,7 @@ public function __construct(StreamInterface $stream) */ public function write($data): void { - $rand = \random_int(0, 28); + $rand = random_int(0, 28); $frame = [ 'fin' => 1, 'rsv1' => 0, @@ -39,7 +39,7 @@ public function write($data): void 'opcode' => 1, // We always send text 'mask' => 1, 'len' => \strlen($data), - 'mask_key' => \substr(\md5(\uniqid()), $rand, 4), + 'mask_key' => substr(md5(uniqid()), $rand, 4), 'data' => $data, ]; @@ -65,12 +65,12 @@ public function write($data): void $this->socketWrite(\chr($secondByte)); if (126 === $len) { - $this->socketWrite(\pack('n', $frame['len'])); + $this->socketWrite(pack('n', $frame['len'])); } elseif (127 === $len) { $higher = $frame['len'] >> 32; $lower = ($frame['len'] << 32) >> 32; - $this->socketWrite(\pack('N', $higher)); - $this->socketWrite(\pack('N', $lower)); + $this->socketWrite(pack('N', $higher)); + $this->socketWrite(pack('N', $lower)); } if (1 === $frame['mask']) { @@ -87,11 +87,11 @@ public function write($data): void * @param int $waitMicroTime Time to wait in microseconds before return false * @param bool $getFrame Whether to return the frame of websocket or only the data * - * @return null|false|string|array Null for socket not available, false for no message, string for the last message and the frame array if $getFrame is set to true + * @return false|string|array|null Null for socket not available, false for no message, string for the last message and the frame array if $getFrame is set to true */ public function read($waitTime = 0, $waitMicroTime = 200000, $getFrame = false) { - if (!\is_resource($this->socket) || \feof($this->socket)) { + if (!\is_resource($this->socket) || feof($this->socket)) { return null; } @@ -99,7 +99,7 @@ public function read($waitTime = 0, $waitMicroTime = 200000, $getFrame = false) $write = null; $expect = null; - if (0 === \stream_select($read, $write, $expect, $waitTime, $waitMicroTime)) { + if (0 === stream_select($read, $write, $expect, $waitTime, $waitMicroTime)) { return false; } @@ -121,9 +121,9 @@ public function read($waitTime = 0, $waitMicroTime = 200000, $getFrame = false) // Get length of the frame if (126 === $frame['len']) { - $frame['len'] = \unpack('n', $this->socketRead(2))[1]; + $frame['len'] = unpack('n', $this->socketRead(2))[1]; } elseif (127 === $frame['len']) { - list($higher, $lower) = \array_values(\unpack('N2', $this->socketRead(8))); + [$higher, $lower] = array_values(unpack('N2', $this->socketRead(8))); $frame['len'] = ($higher << 32) | $lower; } @@ -151,8 +151,6 @@ public function read($waitTime = 0, $waitMicroTime = 200000, $getFrame = false) /** * Force to have something of the expected size (block). * - * @param $length - * * @return string */ private function socketRead($length) @@ -160,8 +158,8 @@ private function socketRead($length) $read = ''; do { - $read .= \fread($this->socket, $length - \strlen($read)); - } while (\strlen($read) < $length && !\feof($this->socket)); + $read .= fread($this->socket, $length - \strlen($read)); + } while (\strlen($read) < $length && !feof($this->socket)); return $read; } @@ -169,12 +167,10 @@ private function socketRead($length) /** * Write to the socket. * - * @param $data - * * @return int */ private function socketWrite($data) { - return \fwrite($this->socket, $data); + return fwrite($this->socket, $data); } } diff --git a/src/Stream/BuildStream.php b/src/Stream/BuildStream.php index b6ba9e705..34f32ac41 100644 --- a/src/Stream/BuildStream.php +++ b/src/Stream/BuildStream.php @@ -12,7 +12,7 @@ class BuildStream extends MultiJsonStream { /** - * [@inheritdoc}. + * {@inheritdoc}. */ protected function getDecodeClass() { diff --git a/src/Stream/CallbackStream.php b/src/Stream/CallbackStream.php index 62b746130..382c935f4 100644 --- a/src/Stream/CallbackStream.php +++ b/src/Stream/CallbackStream.php @@ -19,8 +19,6 @@ public function __construct(StreamInterface $stream) /** * Called when there is a new frame from the stream. - * - * @param callable $onNewFrame */ public function onFrame(callable $onNewFrame): void { @@ -29,8 +27,6 @@ public function onFrame(callable $onNewFrame): void /** * Read a frame in the stream. - * - * @return mixed */ abstract protected function readFrame(); diff --git a/src/Stream/CreateImageStream.php b/src/Stream/CreateImageStream.php index f6cb92ca9..8d5aea7c6 100644 --- a/src/Stream/CreateImageStream.php +++ b/src/Stream/CreateImageStream.php @@ -12,7 +12,7 @@ class CreateImageStream extends MultiJsonStream { /** - * [@inheritdoc}. + * {@inheritdoc}. */ protected function getDecodeClass() { diff --git a/src/Stream/DockerRawStream.php b/src/Stream/DockerRawStream.php index 5f71d629c..1ec7ce8b0 100644 --- a/src/Stream/DockerRawStream.php +++ b/src/Stream/DockerRawStream.php @@ -29,8 +29,6 @@ public function __construct(StreamInterface $stream) /** * Add a callable to read stdin. - * - * @param callable $callback */ public function onStdin(callable $callback): void { @@ -39,8 +37,6 @@ public function onStdin(callable $callback): void /** * Add a callable to read stdout. - * - * @param callable $callback */ public function onStdout(callable $callback): void { @@ -49,8 +45,6 @@ public function onStdout(callable $callback): void /** * Add a callable to read stderr. - * - * @param callable $callback */ public function onStderr(callable $callback): void { @@ -68,7 +62,7 @@ protected function readFrame(): void return; } - $decoded = \unpack('C1type/C3/N1size', $header); + $decoded = unpack('C1type/C3/N1size', $header); $output = $this->forceRead($decoded['size']); $callbackList = []; @@ -92,8 +86,6 @@ protected function readFrame(): void /** * Force to have something of the expected size (block). * - * @param $length - * * @return string */ private function forceRead($length) diff --git a/src/Stream/EventStream.php b/src/Stream/EventStream.php index 0ba9df6f7..1c18a8050 100644 --- a/src/Stream/EventStream.php +++ b/src/Stream/EventStream.php @@ -12,10 +12,10 @@ class EventStream extends MultiJsonStream { /** - * [@inheritdoc}. + * {@inheritdoc}. */ protected function getDecodeClass() { - return 'EventsGetResponse200'; + return 'EventMessage'; } } diff --git a/src/Stream/MultiJsonStream.php b/src/Stream/MultiJsonStream.php index 0973f98a5..536afa171 100644 --- a/src/Stream/MultiJsonStream.php +++ b/src/Stream/MultiJsonStream.php @@ -22,9 +22,6 @@ public function __construct(StreamInterface $stream, SerializerInterface $serial $this->serializer = $serializer; } - /** - * {@inheritdoc} - */ protected function readFrame() { $jsonFrameEnd = false; diff --git a/src/Stream/PushStream.php b/src/Stream/PushStream.php index c40cb6688..4b7242db9 100644 --- a/src/Stream/PushStream.php +++ b/src/Stream/PushStream.php @@ -12,7 +12,7 @@ class PushStream extends MultiJsonStream { /** - * [@inheritdoc}. + * {@inheritdoc}. */ protected function getDecodeClass() { diff --git a/src/Stream/TarStream.php b/src/Stream/TarStream.php index ee0a17519..f14823c89 100644 --- a/src/Stream/TarStream.php +++ b/src/Stream/TarStream.php @@ -4,18 +4,92 @@ namespace Docker\Stream; -use GuzzleHttp\Psr7\Stream; +use Psr\Http\Message\StreamInterface; /** * This class avoid a bug in PHP where fstat return a size of 0 for process stream. */ -class TarStream extends Stream +class TarStream implements StreamInterface { - /** - * {@inheritdoc} - */ - public function getSize() + protected $stream; + + public function __construct(StreamInterface $stream) + { + $this->stream = $stream; + } + + public function __toString() + { + return $this->stream->__toString(); + } + + public function close(): void + { + $this->stream->close(); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize(): ?int { return null; } + + public function tell(): int + { + return $this->stream->tell(); + } + + public function eof(): bool + { + return $this->stream->eof(); + } + + public function isSeekable(): bool + { + return $this->stream->isSeekable(); + } + + public function seek($offset, $whence = \SEEK_SET): void + { + $this->stream->seek($offset, $whence); + } + + public function rewind(): void + { + $this->stream->rewind(); + } + + public function isWritable(): bool + { + return $this->stream->isWritable(); + } + + public function write($string): int + { + return $this->stream->write($string); + } + + public function isReadable(): bool + { + return $this->stream->isReadable(); + } + + public function read($length): string + { + return $this->stream->read($length); + } + + public function getContents(): string + { + return $this->stream->getContents(); + } + + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } } diff --git a/tests/Context/ContextBuilderTest.php b/tests/Context/ContextBuilderTest.php index 828faa63f..613964f01 100644 --- a/tests/Context/ContextBuilderTest.php +++ b/tests/Context/ContextBuilderTest.php @@ -61,11 +61,10 @@ public function testWriteTmpFiles(): void $contextBuilder->add('/foo', 'random content'); $context = $contextBuilder->getContext(); - $filename = \preg_replace(<<getDockerfileContent()); + $filename = preg_replace(<<getDockerfileContent()); $this->assertStringEqualsFile($context->getDirectory().'/'.$filename, 'random content'); } @@ -73,53 +72,50 @@ public function testWriteTmpFiles(): void public function testWriteTmpFileFromStream(): void { $contextBuilder = new ContextBuilder(); - $stream = \fopen('php://temp', 'r+'); - $this->assertSame(7, \fwrite($stream, 'test123')); - \rewind($stream); + $stream = fopen('php://temp', 'r+'); + $this->assertSame(7, fwrite($stream, 'test123')); + rewind($stream); $contextBuilder->addStream('/foo', $stream); $context = $contextBuilder->getContext(); - $filename = \preg_replace(<<getDockerfileContent()); + $filename = preg_replace(<<getDockerfileContent()); $this->assertStringEqualsFile($context->getDirectory().'/'.$filename, 'test123'); } public function testWriteTmpFileFromDisk(): void { $contextBuilder = new ContextBuilder(); - $file = \tempnam('', ''); - \file_put_contents($file, 'abc'); + $file = tempnam('', ''); + file_put_contents($file, 'abc'); $this->assertStringEqualsFile($file, 'abc'); $contextBuilder->addFile('/foo', $file); $context = $contextBuilder->getContext(); - $filename = \preg_replace(<<getDockerfileContent()); + $filename = preg_replace(<<getDockerfileContent()); $this->assertStringEqualsFile($context->getDirectory().'/'.$filename, 'abc'); } public function testWriteTmpDirFromDisk(): void { $contextBuilder = new ContextBuilder(); - $dir = \tempnam(\sys_get_temp_dir(), ''); - \unlink($dir); - \mkdir($dir); - \file_put_contents($dir.'/test', 'abc'); + $dir = tempnam(sys_get_temp_dir(), ''); + unlink($dir); + mkdir($dir); + file_put_contents($dir.'/test', 'abc'); $this->assertStringEqualsFile($dir.'/test', 'abc'); $contextBuilder->addFile('/foo', $dir); $context = $contextBuilder->getContext(); - $filename = \preg_replace(<<getDockerfileContent()); + $filename = preg_replace(<<getDockerfileContent()); $this->assertStringEqualsFile($context->getDirectory().'/'.$filename.'/test', 'abc'); } @@ -130,11 +126,12 @@ public function testWritesAddCommands(): void $context = $contextBuilder->getContext(); - $this->assertRegExp(<<getDockerfileContent() + $this->assertMatchesRegularExpression( + <<getDockerfileContent() ); } @@ -145,10 +142,12 @@ public function testWritesRunCommands(): void $context = $contextBuilder->getContext(); - $this->assertStringEqualsFile($context->getDirectory().'/Dockerfile', <<assertStringEqualsFile( + $context->getDirectory().'/Dockerfile', + <<getContext(); - $this->assertStringEqualsFile($context->getDirectory().'/Dockerfile', <<assertStringEqualsFile( + $context->getDirectory().'/Dockerfile', + <<getContext(); - $this->assertStringEqualsFile($context->getDirectory().'/Dockerfile', <<assertStringEqualsFile( + $context->getDirectory().'/Dockerfile', + <<getContext(); - $this->assertStringEqualsFile($context->getDirectory().'/Dockerfile', <<assertStringEqualsFile( + $context->getDirectory().'/Dockerfile', + <<getContext(); - $this->assertStringEqualsFile($context->getDirectory().'/Dockerfile', <<assertStringEqualsFile( + $context->getDirectory().'/Dockerfile', + <<command('changed'); $content = $contextBuilder->getContext()->getDockerfileContent(); - $this->assertNotContains('CMD test123', $content); + $this->assertStringNotContainsString('CMD test123', $content); $this->assertStringEndsWith("\nCMD changed", $content); } @@ -256,7 +263,7 @@ public function testWritesEntrypointCommand(): void $contextBuilder->entrypoint('changed'); $content = $contextBuilder->getContext()->getDockerfileContent(); - $this->assertNotContains('ENTRYPOINT test123', $content); + $this->assertStringNotContainsString('ENTRYPOINT test123', $content); $this->assertStringEndsWith("\nENTRYPOINT changed", $content); } @@ -266,33 +273,32 @@ public function testTar(): void $contextBuilder->setFormat(Context::FORMAT_TAR); $context = $contextBuilder->getContext(); $content = $context->read(); - $this->assertInternalType('string', $content); + $this->assertIsString($content); $this->assertSame($context->toTar(), $content); } public function testTraverseSymlinks(): void { $contextBuilder = new ContextBuilder(); - $dir = \tempnam('', ''); - \unlink($dir); - \mkdir($dir); + $dir = tempnam('', ''); + unlink($dir); + mkdir($dir); $file = $dir.'/test'; - \file_put_contents($file, 'abc'); + file_put_contents($file, 'abc'); $linkFile = $file.'-symlink'; - \symlink($file, $linkFile); + symlink($file, $linkFile); $contextBuilder->addFile('/foo', $dir); $context = $contextBuilder->getContext(); - $filename = \preg_replace(<<getDockerfileContent()); - \unlink($file); + $filename = preg_replace(<<getDockerfileContent()); + unlink($file); $context->setCleanup(false); $this->assertStringEqualsFile($context->getDirectory().'/'.$filename.'/test-symlink', 'abc'); } diff --git a/tests/Context/ContextTest.php b/tests/Context/ContextTest.php index cd2a113f2..1038213b7 100644 --- a/tests/Context/ContextTest.php +++ b/tests/Context/ContextTest.php @@ -13,10 +13,10 @@ class ContextTest extends TestCase { public function testReturnsValidTarContent(): void { - $directory = __DIR__.DIRECTORY_SEPARATOR.'context-test'; + $directory = __DIR__.\DIRECTORY_SEPARATOR.'context-test'; $context = new Context($directory); - $process = new Process('/usr/bin/env tar c .', $directory); + $process = new Process(['/usr/bin/env', 'tar', 'c', '.'], $directory); $process->run(); $this->assertSame(\strlen($process->getOutput()), \strlen($context->toTar())); @@ -24,10 +24,10 @@ public function testReturnsValidTarContent(): void public function testReturnsValidTarStream(): void { - $directory = __DIR__.DIRECTORY_SEPARATOR.'context-test'; + $directory = __DIR__.\DIRECTORY_SEPARATOR.'context-test'; $context = new Context($directory); - $this->assertInternalType('resource', $context->toStream()); + $this->assertIsResource($context->toStream()); } public function testDirectorySetter(): void @@ -38,19 +38,18 @@ public function testDirectorySetter(): void $this->assertSame('def', $context->getDirectory()); } - /** - * @expectedException \Symfony\Component\Process\Exception\ProcessFailedException - */ public function testTarFailed(): void { - $directory = __DIR__.DIRECTORY_SEPARATOR.'context-test'; - $path = \getenv('PATH'); - \putenv('PATH=/'); + $this->expectException(\Symfony\Component\Process\Exception\ProcessFailedException::class); + + $directory = __DIR__.\DIRECTORY_SEPARATOR.'context-test'; + $path = getenv('PATH'); + putenv('PATH=/'); $context = new Context($directory); try { $context->toTar(); } finally { - \putenv("PATH=$path"); + putenv("PATH=$path"); } } @@ -62,6 +61,6 @@ public function testRemovesFilesOnDestruct(): void unset($context); - $this->assertFileNotExists($file); + $this->assertFileDoesNotExist($file); } } diff --git a/tests/DockerAsyncTest.php b/tests/DockerAsyncTest.php deleted file mode 100644 index 29b2f3bf1..000000000 --- a/tests/DockerAsyncTest.php +++ /dev/null @@ -1,86 +0,0 @@ -assertInstanceOf(DockerAsync::class, DockerAsync::create()); - } - - public function testAsync(): void - { - Loop::run(function () { - $docker = DockerAsync::create(); - - $containerConfig = new ContainersCreatePostBody(); - $containerConfig->setImage('busybox:latest'); - $containerConfig->setCmd(['echo', '-n', 'output']); - $containerConfig->setAttachStdout(true); - $containerConfig->setLabels(new \ArrayObject(['docker-php-test' => 'true'])); - - $response = yield $docker->imageCreate('', [ - 'fromImage' => 'busybox:latest', - ], [], DockerAsync::FETCH_RESPONSE); - - yield $response->getBody(); - - $containerCreate = yield $docker->containerCreate($containerConfig); - $containerStart = yield $docker->containerStart($containerCreate->getId()); - $containerInfo = yield $docker->containerInspect($containerCreate->getId()); - - $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 286419d43..ed09a955c 100644 --- a/tests/DockerClientFactoryTest.php +++ b/tests/DockerClientFactoryTest.php @@ -5,45 +5,44 @@ namespace Docker\Tests; use Docker\DockerClientFactory; -use Http\Client\HttpClient; +use Psr\Http\Client\ClientInterface; class DockerClientFactoryTest extends TestCase { protected function tearDown(): void { parent::tearDown(); - \putenv('DOCKER_TLS_VERIFY'); + putenv('DOCKER_TLS_VERIFY'); } public function testStaticConstructor(): void { - $this->assertInstanceOf(HttpClient::class, DockerClientFactory::create()); + $this->assertInstanceOf(ClientInterface::class, DockerClientFactory::create()); } - /** - * @expectedException \RuntimeException - * @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 { - \putenv('DOCKER_TLS_VERIFY=1'); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Connection to docker has been set to use TLS, but no PATH is defined for certificate in DOCKER_CERT_PATH docker environment variable'); + + putenv('DOCKER_TLS_VERIFY=1'); DockerClientFactory::createFromEnv(); } public function testCreateCustomCa(): void { - \putenv('DOCKER_TLS_VERIFY=1'); - \putenv('DOCKER_CERT_PATH=/tmp'); + putenv('DOCKER_TLS_VERIFY=1'); + putenv('DOCKER_CERT_PATH=/tmp'); - $count = \count(\get_resources('stream-context')); + $count = \count(get_resources('stream-context')); $client = DockerClientFactory::createFromEnv(); - $this->assertInstanceOf(HttpClient::class, $client); + $this->assertInstanceOf(ClientInterface::class, $client); - $contexts = \get_resources('stream-context'); + $contexts = get_resources('stream-context'); $this->assertCount($count + 1, $contexts); // Get the last stream context. - $context = \stream_context_get_options(\end($contexts)); + $context = stream_context_get_options(end($contexts)); $this->assertSame('/tmp/ca.pem', $context['ssl']['cafile']); $this->assertSame('/tmp/cert.pem', $context['ssl']['local_cert']); $this->assertSame('/tmp/key.pem', $context['ssl']['local_pk']); @@ -51,19 +50,19 @@ public function testCreateCustomCa(): void public function testCreateCustomPeerName(): void { - \putenv('DOCKER_TLS_VERIFY=1'); - \putenv('DOCKER_CERT_PATH=/abc'); - \putenv('DOCKER_PEER_NAME=test'); + putenv('DOCKER_TLS_VERIFY=1'); + putenv('DOCKER_CERT_PATH=/abc'); + putenv('DOCKER_PEER_NAME=test'); - $count = \count(\get_resources('stream-context')); + $count = \count(get_resources('stream-context')); $client = DockerClientFactory::createFromEnv(); - $this->assertInstanceOf(HttpClient::class, $client); + $this->assertInstanceOf(ClientInterface::class, $client); - $contexts = \get_resources('stream-context'); + $contexts = get_resources('stream-context'); $this->assertCount($count + 1, $contexts); // Get the last stream context. - $context = \stream_context_get_options(\end($contexts)); + $context = stream_context_get_options(end($contexts)); $this->assertSame('/abc/ca.pem', $context['ssl']['cafile']); $this->assertSame('/abc/cert.pem', $context['ssl']['local_cert']); $this->assertSame('/abc/key.pem', $context['ssl']['local_pk']); diff --git a/tests/Resource/ContainerResourceTest.php b/tests/Resource/ContainerResourceTest.php index d89f3ada9..eb6024a18 100644 --- a/tests/Resource/ContainerResourceTest.php +++ b/tests/Resource/ContainerResourceTest.php @@ -35,13 +35,16 @@ public function testAttach(): void $containerConfig->setImage('busybox:latest'); $containerConfig->setCmd(['echo', '-n', 'output']); $containerConfig->setAttachStdout(true); - $containerConfig->setLabels(new \ArrayObject(['docker-php-test' => 'true'])); + $containerConfig->setLabels(['docker-php-test' => 'true']); $containerCreateResult = $this->getManager()->containerCreate($containerConfig); - $dockerRawStream = $this->getManager()->containerAttach($containerCreateResult->getId(), [ - 'stream' => true, - 'stdout' => true, - ]); + $dockerRawStream = $this->getManager()->containerAttach( + $containerCreateResult->getId(), + [ + 'stream' => true, + 'stdout' => true, + ] + ); $stdoutFull = ''; $dockerRawStream->onStdout(function ($stdout) use (&$stdoutFull): void { @@ -58,6 +61,8 @@ public function testAttach(): void public function testAttachWebsocket(): void { + $this->markTestSkipped('Since docker API 1.28 Websockets are binary so this test needs work. ' . + 'See https://github.com/xtermjs/xterm.js/issues/883'); $containerConfig = new ContainersCreatePostBody(); $containerConfig->setImage('busybox:latest'); $containerConfig->setCmd(['sh']); @@ -69,12 +74,15 @@ public function testAttachWebsocket(): void $containerConfig->setLabels(new \ArrayObject(['docker-php-test' => 'true'])); $containerCreateResult = $this->getManager()->containerCreate($containerConfig); - $webSocketStream = $this->getManager()->containerAttachWebsocket($containerCreateResult->getId(), [ - 'stream' => true, - 'stdout' => true, - 'stderr' => true, - 'stdin' => true, - ]); + $webSocketStream = $this->getManager()->containerAttachWebsocket( + $containerCreateResult->getId(), + [ + 'stream' => true, + 'stdout' => true, + 'stderr' => true, + 'stdin' => true, + ] + ); $this->getManager()->containerStart($containerCreateResult->getId()); @@ -102,21 +110,28 @@ public function testAttachWebsocket(): void public function testLogs(): void { + $this->markTestSkipped('Since at least 1.43 docker API does not return a `application/vnd.docker.raw-stream` ' . + 'but a `application/vnd.docker.multiplexed-stream` so this needs review. '. + 'See https://github.com/beluga-php/docker-php/issues/19'); $containerConfig = new ContainersCreatePostBody(); $containerConfig->setImage('busybox:latest'); $containerConfig->setCmd(['echo', '-n', 'output']); $containerConfig->setAttachStdout(true); - $containerConfig->setLabels(new \ArrayObject(['docker-php-test' => 'true'])); + $containerConfig->setLabels(['docker-php-test' => 'true']); $containerCreateResult = $this->getManager()->containerCreate($containerConfig); $this->getManager()->containerStart($containerCreateResult->getId()); $this->getManager()->containerWait($containerCreateResult->getId()); - $logsStream = $this->getManager()->containerLogs($containerCreateResult->getId(), [ - 'stdout' => true, - 'stderr' => true, - ], Docker::FETCH_OBJECT); + $logsStream = $this->getManager()->containerLogs( + $containerCreateResult->getId(), + [ + 'stdout' => true, + 'stderr' => true, + ], + Docker::FETCH_OBJECT + ); self::assertInstanceOf(DockerRawStream::class, $logsStream); } diff --git a/tests/Resource/ImageResourceTest.php b/tests/Resource/ImageResourceTest.php index e1c129dd0..8e88982da 100644 --- a/tests/Resource/ImageResourceTest.php +++ b/tests/Resource/ImageResourceTest.php @@ -37,7 +37,7 @@ public function testBuild(): void }); $buildStream->wait(); - $this->assertContains('Successfully', $lastMessage); + $this->assertStringContainsString('Successfully', $lastMessage); } public function testCreate(): void @@ -57,7 +57,7 @@ public function testCreate(): void }); $createImageStream->wait(); - $this->assertContains('Pulling from library/registry', $firstMessage); + $this->assertStringContainsString('Pulling from library/registry', $firstMessage); } public function testPushStream(): void @@ -86,6 +86,6 @@ public function testPushStream(): void }); $pushImageStream->wait(); - $this->assertContains('repository [localhost:5000/test-image]', $firstMessage); + $this->assertStringContainsString('repository [localhost:5000/test-image]', $firstMessage); } } diff --git a/tests/Resource/SystemResourceTest.php b/tests/Resource/SystemResourceTest.php index 474f3506e..fb1badf30 100644 --- a/tests/Resource/SystemResourceTest.php +++ b/tests/Resource/SystemResourceTest.php @@ -4,7 +4,7 @@ namespace Docker\Tests\Resource; -use Docker\API\Model\EventsGetResponse200; +use Docker\API\Model\EventMessage; use Docker\Tests\TestCase; class SystemResourceTest extends TestCase @@ -20,8 +20,8 @@ private function getManager() public function testGetEvents(): void { $stream = $this->getManager()->systemEvents([ - 'since' => (string) (\time() - 1), - 'until' => (string) (\time() + 4), + 'since' => (string) (time() - 1), + 'until' => (string) (time() + 4), ]); $lastEvent = null; @@ -36,6 +36,6 @@ public function testGetEvents(): void $stream->wait(); - $this->assertInstanceOf(EventsGetResponse200::class, $lastEvent); + $this->assertInstanceOf(EventMessage::class, $lastEvent); } } diff --git a/tests/Stream/MultiJsonStreamTest.php b/tests/Stream/MultiJsonStreamTest.php index a999f4929..e3683e5c3 100644 --- a/tests/Stream/MultiJsonStreamTest.php +++ b/tests/Stream/MultiJsonStreamTest.php @@ -7,12 +7,12 @@ use Docker\API\Model\BuildInfo; use Docker\Stream\MultiJsonStream; use Docker\Tests\TestCase; -use GuzzleHttp\Psr7\BufferStream; +use Nyholm\Psr7\Stream; use Symfony\Component\Serializer\SerializerInterface; class MultiJsonStreamTest extends TestCase { - public function jsonStreamDataProvider() + public static function jsonStreamDataProvider() { return [ [ @@ -20,7 +20,7 @@ public function jsonStreamDataProvider() ['{}', '{"abc":"def"}'], ], [ - '{"test": "abc\"\""}', + '{"test": "abc\"\""}', ['{"test":"abc\"\""}'], ], [ @@ -31,25 +31,28 @@ public function jsonStreamDataProvider() } /** - * @param $jsonStream - * @param $jsonParts * @dataProvider jsonStreamDataProvider */ public function testReadJsonEscapedDoubleQuote(string $jsonStream, array $jsonParts): void { - $stream = new BufferStream(); - $stream->write($jsonStream); + $stream = Stream::create($jsonStream); + $stream->rewind(); $serializer = $this->getMockBuilder(SerializerInterface::class) ->getMock(); + $callIndex = 0; $serializer ->expects($this->exactly(\count($jsonParts))) ->method('deserialize') - ->withConsecutive(...\array_map(function ($part) { - return [$part, BuildInfo::class, 'json', []]; - }, $jsonParts)) - ; + ->willReturnCallback(function ($data, $class, $format, $context) use ($jsonParts, &$callIndex) { + $this->assertEquals($jsonParts[$callIndex], $data); + $this->assertEquals(BuildInfo::class, $class); + $this->assertEquals('json', $format); + $this->assertEquals([], $context); + $callIndex++; + return null; + }); $stub = $this->getMockForAbstractClass(MultiJsonStream::class, [$stream, $serializer]); $stub->expects($this->any())