diff --git a/.gitignore b/.gitignore index 0ed6b40..ca2dd27 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,13 @@ client.key .c9 .env .directory + +# IDE & System +.DS_Store +/.idea +/.vscode +.phpstorm.meta.php +.phpunit.result.cache +Thumbs.db +/*.iml + diff --git a/.travis.yml b/.travis.yml index d7b28aa..82eb26f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,10 @@ language: php sudo: false php: - - 5.5 - - 5.6 - - 7.0 + - 7.3 + - 7.4 + - 8.0 + - 8.1 cache: directories: - $HOME/.composer/cache @@ -12,4 +13,4 @@ before_install: install: - travis_retry composer update --no-interaction --prefer-source script: - - composer test \ No newline at end of file + - composer test diff --git a/README.md b/README.md index ef6bb0a..6d9a1b8 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,19 @@ Via Composer $ composer require dhope0000/lxd ``` +For usage of this library any httpclient library is needed. If you don't already use one in your project, please install one in advance. + +``` bash +$ composer require php-http/guzzle7-adapter +``` + +## Install for usage with Guzzle 6 + +``` bash +$ composer require php-http/guzzle6-adapter +$ composer require dhope0000/lxd "^0.24" +``` + ## Usage See the [`docs`](./docs) for more information. diff --git a/composer.json b/composer.json index 62dd492..518d283 100644 --- a/composer.json +++ b/composer.json @@ -25,18 +25,18 @@ } ], "require": { - "php": "~5.5|~7.0", + "php": "^7.3 || ^8.0", "psr/http-message": "^1.0", - "php-http/httplug": "^1.0", - "php-http/discovery": "^1.0", + "php-http/httplug": "^2.2", + "php-http/discovery": "^1.12", "php-http/client-implementation": "^1.0", - "php-http/client-common": "^1.1", + "php-http/client-common": "^2.3", "php-http/cache-plugin": "^1.6" }, "require-dev": { "phpunit/phpunit": "4.*", "mockery/mockery": "^0.9.5", - "php-http/guzzle6-adapter": "^1.0", + "php-http/guzzle7-adapter": "^1.0", "guzzlehttp/psr7": "^1.2", "php-http/mock-client": "^0.3", "squizlabs/php_codesniffer": "^2.6" diff --git a/src/Endpoint/InstaceBase.php b/src/Endpoint/InstaceBase.php index 4190aae..2df7e3d 100644 --- a/src/Endpoint/InstaceBase.php +++ b/src/Endpoint/InstaceBase.php @@ -573,7 +573,7 @@ public function remove($name, $wait = false) * @param bool $record Whether to store stdout and stderr * @param array $environment An associative array, the key will be the environment variable name * @param bool $wait Wait for operation to finish - * @return object + * @return array|string */ public function execute($name, $command, $record = false, array $environment = [], $wait = false) { @@ -601,21 +601,24 @@ public function execute($name, $command, $record = false, array $environment = [ $response = $this->post($this->getEndpoint().$name.'/exec', $opts, $config); if ($wait) { - $response = $this->client->operations->wait($response['id']); - $logs = []; - $output = $response['metadata']['output']; - $return = $response['metadata']['return']; - unset($response); - - foreach ($output as $log) { - $response['output'][] = str_replace( - '/'.$this->client->getApiVersion().$this->getEndpoint().$name.'/logs/', - '', - $log - ); + $waitResponse = $this->client->operations->wait($response['id']); + + if ($record === true) { + $output = isset($waitResponse['metadata']['output']) ? $waitResponse['metadata']['output'] : []; + $return = $waitResponse['metadata']['return']; + unset($waitResponse); + $response = []; + + foreach ($output as $log) { + $response['output'][] = str_replace( + '/' . $this->client->getApiVersion() . $this->getEndpoint() . $name . '/logs/', + '', + $log + ); + } + + $response['return'] = $return; } - - $response['return'] = $return; } return $response; diff --git a/src/Endpoint/Instance/Logs.php b/src/Endpoint/Instance/Logs.php index 419343c..e525dc7 100644 --- a/src/Endpoint/Instance/Logs.php +++ b/src/Endpoint/Instance/Logs.php @@ -3,6 +3,8 @@ namespace Opensaucesystems\Lxd\Endpoint\Instance; use Opensaucesystems\Lxd\Endpoint\AbstractEndpoint; +use Opensaucesystems\Lxd\Exception\InvalidEndpointException; +use Opensaucesystems\Lxd\Helpers\Str; class Logs extends AbstractEndpoint { @@ -62,4 +64,20 @@ public function remove($name, $log) { return $this->delete($this->getEndpoint().$name.'/logs/'.$log); } + + public function __get($endpoint) + { + $className = basename(str_replace('\\', '/', get_class($this))); + $class = __NAMESPACE__.'\\'.$className.'\\'.Str::studly($endpoint); + + if (class_exists($class)) { + $class = new $class($this->client); + $class->setEndpoint($this->getEndpoint()); + return $class; + } else { + throw new InvalidEndpointException( + 'Endpoint '.$class.', not implemented.' + ); + } + } } diff --git a/src/Endpoint/Instance/Logs/ExecOutput.php b/src/Endpoint/Instance/Logs/ExecOutput.php new file mode 100644 index 0000000..f02bb5c --- /dev/null +++ b/src/Endpoint/Instance/Logs/ExecOutput.php @@ -0,0 +1,65 @@ +endpoint; + } + + public function setEndpoint(string $endpoint) + { + $this->endpoint = $endpoint; + } + + /** + * List of exec record-output logs for a container + * + * @param string $name Name of container + * @return array + */ + public function all($name) + { + $logs = []; + + foreach ($this->get($this->getEndpoint().$name.'/logs/exec-output/') as $log) { + $logs[] = str_replace( + '/'.$this->client->getApiVersion().$this->getEndpoint().$name.'/logs/exec-output/', + '', + $log + ); + } + + return $logs; + } + + /** + * Get the contents of a particular exec record-output log file + * + * @param string $name Name of container + * @param string $log Name of log + * @return object + */ + public function read($name, $log) + { + return $this->get($this->getEndpoint().$name.'/logs/exec-output/'.$log); + } + + /** + * Remove a particular exec record-output log file + * + * @param string $name Name of container + * @param string $log Name of log + * @return object + */ + public function remove($name, $log) + { + return $this->delete($this->getEndpoint().$name.'/logs/exec-output/'.$log); + } +} diff --git a/src/Helpers/Str.php b/src/Helpers/Str.php new file mode 100644 index 0000000..9598b98 --- /dev/null +++ b/src/Helpers/Str.php @@ -0,0 +1,38 @@ +getUri()->getPath(); $uri = $request->getUri()->withPath($this->path.$currentPath); diff --git a/src/HttpClient/Plugin/PathTrimEnd.php b/src/HttpClient/Plugin/PathTrimEnd.php index 2eeb4e3..c75485e 100644 --- a/src/HttpClient/Plugin/PathTrimEnd.php +++ b/src/HttpClient/Plugin/PathTrimEnd.php @@ -2,6 +2,7 @@ namespace Opensaucesystems\Lxd\HttpClient\Plugin; use Http\Client\Common\Plugin; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; /** @@ -23,7 +24,7 @@ public function __construct($trim = '/') /** * {@inheritdoc} */ - public function handleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $trimPath = rtrim($request->getUri()->getPath(), $this->trim); $uri = $request->getUri()->withPath($trimPath); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 78e9b8f..26f9d5f 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -4,10 +4,11 @@ use Mockery; use Opensaucesystems\Lxd\Client; +use PHPUnit\Framework\TestCase; -class ClientTest extends \PHPUnit_Framework_TestCase +class ClientTest extends TestCase { - protected function tearDown() + protected function tearDown(): void { Mockery::close(); } @@ -66,17 +67,14 @@ public function testSetUrl() $this->assertEquals($url, $client->getUrl()); } - /** - * Test that endpoint doesn't exist - * - * @expectedException \Opensaucesystems\Lxd\Exception\InvalidEndpointException - */ public function testInvalidEndpointException() { $httpClient = Mockery::mock('\Http\Client\HttpClient'); $client = new Client($httpClient); + $this->expectException(\Opensaucesystems\Lxd\Exception\InvalidEndpointException::class); + $client->nonEndpoint; } } diff --git a/tests/Endpoint/ImagesTest.php b/tests/Endpoint/ImagesTest.php index 1a8a411..af9d96d 100644 --- a/tests/Endpoint/ImagesTest.php +++ b/tests/Endpoint/ImagesTest.php @@ -359,12 +359,6 @@ public function testCreateFromRemoteWithFingerprintAndProtocolAndSecretAndCertif $this->assertEquals($expectedValue, $endpoint->createFromRemote($server, $options, $autoUpdate, $wait)); } - /** - * Test creating - * - * @expectedException Exception - * @expectedExceptionMessage Invalid protocol. Valid choices: lxd, simplestreams - */ public function testCreateFromRemoteWithIncorrectProtocol() { $server = "https://images.linuxcontainers.org:8443"; @@ -380,15 +374,12 @@ public function testCreateFromRemoteWithIncorrectProtocol() $endpoint = $this->getEndpointMock($this->getEndpointClass()); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Invalid protocol. Valid choices: lxd, simplestreams'); + $endpoint->createFromRemote($server, $options, $autoUpdate, $wait); } - /** - * Test creating - * - * @expectedException Exception - * @expectedExceptionMessage Alias or Fingerprint must be set - */ public function testCreateFromRemoteWithNoAliasOrFingerprint() { $server = "https://images.linuxcontainers.org:8443"; @@ -400,6 +391,9 @@ public function testCreateFromRemoteWithNoAliasOrFingerprint() $endpoint = $this->getEndpointMock($this->getEndpointClass()); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Alias or Fingerprint must be set'); + $endpoint->createFromRemote($server, $options, $autoUpdate, $wait); } @@ -611,13 +605,12 @@ public function testRemoveImageWithWaitTrue() $this->assertEquals([], $endpoint->remove($fingerprint)); } - /** - * @expectedException Opensaucesystems\Lxd\Exception\InvalidEndpointException - */ public function testGetEndpointDoesNotExist() { $endpoint = $this->getEndpointMock($this->getEndpointClass()); + $this->expectException(\Opensaucesystems\Lxd\Exception\InvalidEndpointException::class); + $endpoint->__get('invalidendpoint'); } } diff --git a/tests/Endpoint/TestCase.php b/tests/Endpoint/TestCase.php index 4cf0fe8..ef1a96d 100644 --- a/tests/Endpoint/TestCase.php +++ b/tests/Endpoint/TestCase.php @@ -2,13 +2,15 @@ namespace Opensaucesystems\Lxd\Tests\Endpoint; -abstract class TestCase extends \PHPUnit_Framework_TestCase +use PHPUnit\Framework\TestCase as FrameworkTestCase; + +abstract class TestCase extends FrameworkTestCase { abstract protected function getEndpointClass(); protected function getEndpointMock($endpointClass, $wait = false, $expectedValue = null) { - $httpClient = $this->getMock('Http\Client\HttpClient', ['sendRequest']); + $httpClient = $this->getMockBuilder('Http\Client\HttpClient')->onlyMethods(['sendRequest'])->getMock(); $httpClient ->expects($this->any()) @@ -16,12 +18,12 @@ protected function getEndpointMock($endpointClass, $wait = false, $expectedValue if ($wait) { $client = $this->getMockBuilder('\Opensaucesystems\Lxd\Client') - ->setMethods(['__get']) - ->setConstructorArgs([$httpClient]) - ->getMock(); - + ->onlyMethods(['__get']) + ->setConstructorArgs([$httpClient]) + ->getMock(); + $operations = $this->getMockBuilder('Opensaucesystems\Lxd\Endpoint\Operations') - ->setMethods(['wait']) + ->onlyMethods(['wait']) ->setConstructorArgs([$client]) ->getMock(); @@ -39,7 +41,7 @@ protected function getEndpointMock($endpointClass, $wait = false, $expectedValue } return $this->getMockBuilder($endpointClass) - ->setMethods(['get', 'post', 'patch', 'delete', 'put']) + ->onlyMethods(['get', 'post', 'patch', 'delete', 'put']) ->setConstructorArgs([$client]) ->getMock(); } diff --git a/tests/HttpClient/Message/ResponseMediatorTest.php b/tests/HttpClient/Message/ResponseMediatorTest.php index 057a629..de412f1 100644 --- a/tests/HttpClient/Message/ResponseMediatorTest.php +++ b/tests/HttpClient/Message/ResponseMediatorTest.php @@ -4,8 +4,9 @@ use Opensaucesystems\Lxd\HttpClient\Message\ResponseMediator; use GuzzleHttp\Psr7\Response; +use PHPUnit\Framework\TestCase; -class ResponseMediatorTest extends \PHPUnit_Framework_TestCase +class ResponseMediatorTest extends TestCase { public function testGetContent() { @@ -13,7 +14,7 @@ public function testGetContent() $response = new Response( 200, array('Content-Type'=>'application/json'), - \GuzzleHttp\Psr7\stream_for(json_encode($body)) + \GuzzleHttp\Psr7\Utils::streamFor(json_encode($body)) ); $this->assertEquals($body['metadata'], ResponseMediator::getContent($response)); @@ -28,7 +29,7 @@ public function testGetContentNotJson() $response = new Response( 200, array(), - \GuzzleHttp\Psr7\stream_for($body) + \GuzzleHttp\Psr7\Utils::streamFor($body) ); $this->assertEquals($body, ResponseMediator::getContent($response)); @@ -43,7 +44,7 @@ public function testGetContentInvalidJson() $response = new Response( 200, array('Content-Type'=>'application/json'), - \GuzzleHttp\Psr7\stream_for($body) + \GuzzleHttp\Psr7\Utils::streamFor($body) ); $this->assertEquals($body, ResponseMediator::getContent($response));