From 7c67d68a12a7e96f05b44e74ea122076186e83c6 Mon Sep 17 00:00:00 2001 From: Nicolas MURE Date: Thu, 21 Sep 2017 19:35:37 +0200 Subject: [PATCH] opencloud to openstack adapter rewrite --- .env.dist | 17 +- .travis.yml | 6 +- CHANGELOG.md | 12 + README.md | 2 +- UPGRADE.md | 10 + composer.json | 8 +- doc/adapters/open-cloud.md | 83 ---- doc/adapters/open-stack.md | 111 +++++ doc/couscous.yml | 6 +- phpunit.xml.dist | 5 - spec/Gaufrette/Adapter/OpenCloudSpec.php | 197 --------- spec/Gaufrette/Adapter/OpenStackSpec.php | 414 ++++++++++++++++++ spec/Gaufrette/FilesystemSpec.php | 2 +- src/Gaufrette/Adapter.php | 6 +- src/Gaufrette/Adapter/OpenCloud.php | 253 ----------- src/Gaufrette/Adapter/OpenStack.php | 350 +++++++++++++++ .../ObjectStoreFactory.php | 55 --- .../ObjectStoreFactoryInterface.php | 18 - src/Gaufrette/Filesystem.php | 25 +- .../Functional/Adapter/FunctionalTestCase.php | 1 - .../Functional/Adapter/OpenCloudTest.php | 60 --- .../Adapter/OpenStack/IdentityV2Test.php | 55 +++ .../Adapter/OpenStack/IdentityV3Test.php | 40 ++ .../Adapter/OpenStack/OpenStackTestCase.php | 79 ++++ 24 files changed, 1122 insertions(+), 693 deletions(-) delete mode 100644 doc/adapters/open-cloud.md create mode 100644 doc/adapters/open-stack.md delete mode 100644 spec/Gaufrette/Adapter/OpenCloudSpec.php create mode 100644 spec/Gaufrette/Adapter/OpenStackSpec.php delete mode 100644 src/Gaufrette/Adapter/OpenCloud.php create mode 100644 src/Gaufrette/Adapter/OpenStack.php delete mode 100644 src/Gaufrette/Adapter/OpenStackCloudFiles/ObjectStoreFactory.php delete mode 100644 src/Gaufrette/Adapter/OpenStackCloudFiles/ObjectStoreFactoryInterface.php delete mode 100644 tests/Gaufrette/Functional/Adapter/OpenCloudTest.php create mode 100644 tests/Gaufrette/Functional/Adapter/OpenStack/IdentityV2Test.php create mode 100644 tests/Gaufrette/Functional/Adapter/OpenStack/IdentityV3Test.php create mode 100644 tests/Gaufrette/Functional/Adapter/OpenStack/OpenStackTestCase.php diff --git a/.env.dist b/.env.dist index 0883ec207..5c79dddaa 100644 --- a/.env.dist +++ b/.env.dist @@ -7,9 +7,22 @@ GCS_PROJECT_ID= GCS_BUCKET_NAME= GCS_JSON_KEY_FILE=/app/secrets/gaufrette-appId.json +# OpenStack identity v3 +# see /doc/adapters/open-stack.md +IBMCLOUD_USERID= +IBMCLOUD_PASSWORD= +IBMCLOUD_REGION= + MONGO_URI=mongodb://mongodb:27017 MONGO_DBNAME=gridfs_test +# OpenStack identity v2 +# see /doc/adapters/open-stack.md +RACKSPACE_USERNAME= +RACKSPACE_PASSWORD= +RACKSPACE_TENANT_ID= +RACKSPACE_REGION= + SFTP_HOST=sftp SFTP_PORT=22 SFTP_USER=gaufrette @@ -25,7 +38,3 @@ FTP_BASE_DIR=/gaufrette AZURE_ACCOUNT= AZURE_KEY= AZURE_CONTAINER= - -RACKSPACE_USER= -RACKSPACE_APIKEY= -RACKSPACE_CONTAINER= diff --git a/.travis.yml b/.travis.yml index f0662e388..9e966a4d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,9 +26,9 @@ env: matrix: include: - - php: '7.1' - - php: '7.2' - - php: '7.3' + - php: '7.1.29' + - php: '7.2.18' + - php: '7.3.5' before_install: - echo "extension = mongodb.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; diff --git a/CHANGELOG.md b/CHANGELOG.md index b621e337b..171688f1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,20 @@ v1.0.0 ## New features +- The [OpenCloud adapter](https://github.com/KnpLabs/Gaufrette/blob/v0.5.0/src/Gaufrette/Adapter/OpenCloud.php) +has been replaced by the [OpenStack adapter](/src/Gaufrette/Adapter/OpenStack.php). +The new adapter now uses the https://github.com/php-opencloud/openstack SDK +which is the latest supported version of the SDK for OpenStack instead of +https://github.com/rackspace/php-opencloud (#533). - Google Cloud Storage Adapter (#557) +## Removed + +- The [OpenCloud adapter](https://github.com/KnpLabs/Gaufrette/blob/v0.5.0/src/Gaufrette/Adapter/OpenCloud.php) +has been removed. +- The [ObjectStoreFactory](https://github.com/KnpLabs/Gaufrette/blob/v0.5.0/src/Gaufrette/Adapter/OpenStackCloudFiles/ObjectStoreFactory.php) +has been removed. + Thank you @nicolasmure and @PanzerLlama for your contributions ! v0.8.3 diff --git a/README.md b/README.md index 555b3604a..baa2031a5 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ your issue or pull request in a timely manner, ping us: | GridFS | @NiR- | | InMemory | | | Local | | -| OpenCloud | @NiR- | +| OpenStack | @nicolasmure | | PhpseclibSftp | @fabschurt | | Zip | | diff --git a/UPGRADE.md b/UPGRADE.md index 8d052ff3e..11d716416 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,6 +1,16 @@ 1.0 === +**Gaufrette\Adapter\OpenStackCloudFiles\ObjectStoreFactory:** +* This factory has been removed + +**Gaufrette\Adapter\OpenCloud:** +* This adapter has been removed and is now replaced by +`Gaufrette\Adapter\OpenStack`. Additionally, the +[`rackspace/php-opencloud`](https://github.com/rackspace/php-opencloud) SDK +was replaced by the +[`php-opencloud/openstack`](https://github.com/php-opencloud/openstack) SDK. + **Gaufrette\Adapter\SafeLocal:** * This adapter has been removed and will be superseded. diff --git a/composer.json b/composer.json index 2c14d7659..f855248dc 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,6 @@ "require-dev": { "aws/aws-sdk-php": "^2.4.12||~3", "amazonwebservices/aws-sdk-for-php": "1.5.*", - "rackspace/php-opencloud" : "^1.9.2", "google/apiclient": "~1.1.3", "phpspec/phpspec": "~5.1", "phpseclib/phpseclib": "^2.0", @@ -37,7 +36,8 @@ "microsoft/azure-storage-blob": "^1.0", "akeneo/phpspec-skip-example-extension": "^4.0", "liuggio/fastest": "^1.6", - "google/cloud-storage": "~1.0" + "google/cloud-storage": "~1.0", + "php-opencloud/openstack": "~3.0.0" }, "suggest": { "knplabs/knp-gaufrette-bundle": "to use with Symfony", @@ -49,13 +49,13 @@ "gaufrette/aws-s3-adapter": "to use AwsS3 adapter (supports SDK v2 and v3)", "gaufrette/in-memory-adapter": "to use InMemory adapter", "gaufrette/local-adapter": "to use Local adapter", - "gaufrette/opencloud-adapter": "to use Opencloud adapter", + "gaufrette/openstack-adapter": "to use OpenStack adapter", "gaufrette/zip-adapter": "to use Zip adapter", "gaufrette/gridfs-adapter": "to use GridFS adapter", "google/cloud-storage": "to use GoogleCloudStorage adapter", "ext-curl": "*", "ext-mbstring": "*", - "ext-fileinfo": "This extension is used to automatically detect the content-type of a file in the AwsS3, OpenCloud, AzureBlogStorage and GoogleCloudStorage adapters", + "ext-fileinfo": "This extension is used to automatically detect the content-type of a file in the AwsS3, OpenStack, AzureBlogStorage and GoogleCloudStorage adapters", "ext-mongodb": "*", "mongodb/mongodb": "*" }, diff --git a/doc/adapters/open-cloud.md b/doc/adapters/open-cloud.md deleted file mode 100644 index 00ef34f74..000000000 --- a/doc/adapters/open-cloud.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -currentMenu: open-cloud ---- - -# OpenCloud & LazyOpenCloud - -**Warning: LazyOpenCloud adapter has been deprecated since v0.4.0 and will be removed in v1.0.0.** - -First, you will need to install the adapter: -```bash -composer require gaufrette/opencloud-adapter -``` - -To use the OpenCloud adapter you will need to create a connection using the [OpenCloud SDK](https://github.com/rackspace/php-opencloud). -You can then fetch the ObjectStore which is required for the OpenCloud adapter. - -## OpenCloud Example - -```php - 'your username', - 'password' => 'your Keystone password', - 'tenantName' => 'your tenant (project) name' - ) -); -$objectStore = $connection->objectStoreService('cloudFiles', 'LON', 'publicURL'); - -$adapter = new OpenCloudAdapter( - $objectStore, - 'container-name' -); -$filesystem = new Filesystem($adapter); -``` - -## Rackspace Example - -Rackspace uses a difference connection class - -```php - 'rackspace-user', - 'apiKey' => '0900af093093788912388fc09dde090ffee09' - ) -); - -$objectStore = $connection->objectStoreService('cloudFiles', 'LON', 'publicURL'); - -$adapter = new OpenCloudAdapter( - $objectStore, - 'container-name' -); -$filesystem = new Filesystem($adapter); -``` - -## LazyOpenCloud Example - -Instantiating the OpenCloud object store service has some overhead because it issues an authentication request, -even if you end up not using the filesystem. For better performance you can use a lazy-loading adapter which only authenticates when needed. - -```php -objectStoreV1() +; + +/* + * @see \OpenStack\ObjectStore\v1\Api::putContainer for the list of options + */ +$objectStore->createContainer([ + 'name' => 'my-container', +]); +``` + +## Usage with Identity API v3 + +For services using the [OpenStack Identity API v3](https://developer.openstack.org/api-ref/identity/v3/index.html), +such as [IBM Cloud](https://www.ibm.com/cloud/) : + +```php +use Gaufrette\Adapter\OpenStack as OpenStackAdapter; +use Gaufrette\Filesystem; +use OpenStack\OpenStack; + +$objectStore = (new OpenStack([ + 'user' => [ + 'id' => 'the user ID related to the storage service', + 'password' => 'the user password related to the storage service', + ], + 'authUrl' => 'https://example.com/v2/identity', + 'region' => 'the cloud region (eg "london")', + ])) + ->objectStoreV1() +; + +$adapter = new OpenStackAdapter( + $objectStore, + 'container-name' +); + +$filesystem = new Filesystem($adapter); +``` + +To find the options to use with IBM Cloud, [create a new project](https://console.bluemix.net/developer/appservice/starter-kits) +and add an ObjectStorage to the project. The storage will be configured +automatically and you'll be able to see its service credentials then. + +## Usage with Identity API v2 + +For services using the [OpenStack Identity API v2](https://developer.openstack.org/api-ref/identity/v2/), +such as [rackspace.com](https://www.rackspace.com/) : + +```php +use Gaufrette\Adapter\OpenStack as OpenStackAdapter; +use Gaufrette\Filesystem; +use GuzzleHttp\Client; +use GuzzleHttp\HandlerStack; +use OpenStack\Identity\v2\Service as IdentityService; +use OpenStack\OpenStack; + +$objectStore = new (OpenStack([ + 'username' => 'your username', + 'password' => 'your password', + 'tenantId' => 'your tenant Id (also known as account Id/number)' + 'authUrl' => 'https://example.com/v2/identity', + 'region' => 'the cloud region (eg "LON" for London)', + 'identityService' => IdentityService::factory( + new Client([ + 'base_uri' => 'https://example.com/v2/identity', + 'handler' => HandlerStack::create(), + ]) + ), + ])) + ->objectStoreV1([ + 'catalogName' => 'cloudFiles', // default to "swift", use "cloudFiles" for rackspace, + // or find the catalog name of your cloud service + // associated with the "object-store" catalog type + ]) +; + +$adapter = new OpenStackAdapter( + $objectStore, + 'container-name' +); + +$filesystem = new Filesystem($adapter); +``` + +## Links + +- Go [here](https://github.com/php-opencloud/openstack/blob/master/src/OpenStack.php) +to see all OpenStack connection options. diff --git a/doc/couscous.yml b/doc/couscous.yml index 75443ed74..25f33f286 100644 --- a/doc/couscous.yml +++ b/doc/couscous.yml @@ -68,9 +68,9 @@ menu: local: text: Local relativeUrl: adapters/local.html - open-cloud: - text: OpenCloud - relativeUrl: adapters/open-cloud.html + open-stack: + text: OpenStack + relativeUrl: adapters/open-stack.html phpseclib-sftp: text: PhpseclibSftp relativeUrl: adapters/phpseclib-sftp.html diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ad116a588..eb20fd9eb 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -37,11 +37,6 @@ - - - diff --git a/spec/Gaufrette/Adapter/OpenCloudSpec.php b/spec/Gaufrette/Adapter/OpenCloudSpec.php deleted file mode 100644 index 7a5075618..000000000 --- a/spec/Gaufrette/Adapter/OpenCloudSpec.php +++ /dev/null @@ -1,197 +0,0 @@ - - * @author Daniel Richter - */ -class OpenCloudSpec extends ObjectBehavior -{ - function let(Service $objectStore, Container $container) - { - $objectStore->getContainer("test")->willReturn($container); - $this->beConstructedWith($objectStore, 'test', false); - } - - function it_is_adapter() - { - $this->shouldHaveType('Gaufrette\Adapter'); - } - - function it_reads_file(Container $container, DataObject $object) - { - $object->getContent()->willReturn("Hello World"); - $container->getObject("test")->willReturn($object); - - $this->read('test')->shouldReturn('Hello World'); - } - - function it_reads_file_on_error_returns_false(Container $container) - { - $container->getObject("test")->willThrow(new ObjectNotFoundException()); - - $this->read('test')->shouldReturn(false); - } - - function it_writes_file_returns_size(Container $container, DataObject $object) - { - $testData = "Hello World!"; - $testDataSize = strlen($testData); - - $object->getContentLength()->willReturn($testDataSize); - $container->uploadObject('test', $testData)->willReturn($object); - - $this->write('test', $testData)->shouldReturn($testDataSize); - } - - function it_writes_file_and_write_fails_returns_false(Container $container) - { - $testData = "Hello World!"; - - $container->uploadObject('test', $testData)->willThrow(new CreateUpdateError()); - - $this->write('test', $testData)->shouldReturn(false); - } - - function it_returns_true_if_key_exists(Container $container, DataObject $object) - { - $container->getPartialObject('test')->willReturn($object); - - $this->exists('test')->shouldReturn(true); - } - - function it_returns_false_if_key_does_not_exist(Container $container) - { - $container->getPartialObject('test')->willThrow(new BadResponseException()); - - $this->exists('test')->shouldReturn(false); - } - - function it_deletes_file_on_success_returns_true(Container $container, DataObject $object) - { - $object->delete()->willReturn(null); - $container->getObject("test")->willReturn($object); - - $this->delete('test')->shouldReturn(true); - } - - function it_deletes_file_returns_false_on_failure(Container $container, DataObject $object) - { - $object->delete()->willThrow(new DeleteError()); - $container->getObject("test")->willReturn($object); - - $this->delete('test')->shouldReturn(false); - } - - function it_deletes_file_if_file_does_not_exist_returns_false(Container $container) - { - $container->getObject("test")->willThrow(new ObjectNotFoundException()); - - $this->delete('test')->shouldReturn(false); - } - - function it_returns_checksum_if_file_exists(Container $container, DataObject $object) - { - $object->getEtag()->willReturn("test String"); - $container->getObject("test")->willReturn($object); - - $this->checksum('test')->shouldReturn("test String"); - } - - function it_returns_false_when_file_does_not_exist(Container $container) - { - $container->getObject("test")->willThrow(new ObjectNotFoundException()); - - $this->checksum('test')->shouldReturn(false); - } - - function it_returns_files_as_sorted_array(Container $container, Collection $objectList, DataObject $object1, DataObject $object2, DataObject $object3) - { - $outputArray = array('key1', 'key2', 'key5'); - $index = 0; - - $object1->getName()->willReturn('key5'); - $object2->getName()->willReturn('key2'); - $object3->getName()->willReturn('key1'); - - $objects = array($object1, $object2, $object3); - - $objectList->next()->will( - function () use ($objects, &$index) { - if ($index < count($objects)) { - $index++; - - return $objects[$index - 1]; - } - } - ) ->shouldBeCalledTimes(count($objects) + 1); - - $container->objectList()->willReturn($objectList); - - $this->keys()->shouldReturn($outputArray); - } - - function it_throws_exception_if_container_does_not_exist(Service $objectStore) - { - $containerName = 'container-does-not-exist'; - - $objectStore->getContainer($containerName)->willThrow(new BadResponseException()); - $this->beConstructedWith($objectStore, $containerName); - - $this->shouldThrow('\RuntimeException')->duringExists('test'); - } - - function it_creates_container(Service $objectStore, Container $container) - { - $containerName = 'container-does-not-yet-exist'; - $filename = 'test'; - - $objectStore->getContainer($containerName)->willThrow(new BadResponseException()); - $objectStore->createContainer($containerName)->willReturn($container); - $container->getPartialObject($filename)->willThrow(new BadResponseException()); - - $this->beConstructedWith($objectStore, $containerName, true); - - $this->exists($filename)->shouldReturn(false); - } - - function it_throws_exeption_if_container_creation_fails(Service $objectStore) - { - $containerName = 'container-does-not-yet-exist'; - - $objectStore->getContainer($containerName)->willThrow(new BadResponseException()); - $objectStore->createContainer($containerName)->willReturn(false); - - $this->beConstructedWith($objectStore, $containerName, true); - - $this->shouldThrow('\RuntimeException')->duringExists('test'); - } - - function it_returns_false_if_the_object_does_not_exists_when_fetching_mtime(Container $container) - { - $container->getObject('foo')->willThrow(ObjectNotFoundException::class); - - $this->mtime('foo')->shouldReturn(false); - } - - function it_fetches_file_mtime(DataObject $object, Container $container) - { - $container->getObject('foo')->willReturn($object); - $object->getLastModified()->willReturn('Tue, 13 Jun 2017 22:02:34 GMT'); - - $this->mtime('foo')->shouldReturn('1497391354'); - } -} diff --git a/spec/Gaufrette/Adapter/OpenStackSpec.php b/spec/Gaufrette/Adapter/OpenStackSpec.php new file mode 100644 index 000000000..b2af672b7 --- /dev/null +++ b/spec/Gaufrette/Adapter/OpenStackSpec.php @@ -0,0 +1,414 @@ + + * @author Daniel Richter + * @author Nicolas MURE + */ +class OpenStackSpec extends ObjectBehavior +{ + function let(Service $objectStore, Container $container) + { + $objectStore->containerExists('test')->willReturn(true); + $objectStore->getContainer('test')->willReturn($container); + $this->beConstructedWith($objectStore, 'test', false); + } + + function it_is_adapter() + { + $this->shouldHaveType('Gaufrette\Adapter'); + } + + function it_throws_exception_when_not_able_to_determine_if_container_exist(Service $objectStore) + { + $containerName = 'container-does-not-exist'; + + $objectStore->containerExists($containerName)->willThrow($this->getBadResponseError(400)); + $this->beConstructedWith($objectStore, $containerName); + + $this->shouldThrow('\RuntimeException')->duringExists('test'); + } + + function it_throws_exception_if_container_does_not_exist(Service $objectStore) + { + $containerName = 'container-does-not-exist'; + + $objectStore->containerExists($containerName)->willReturn(false); + $this->beConstructedWith($objectStore, $containerName); + + $this->shouldThrow('\RuntimeException')->duringExists('test'); + } + + function it_reads_file(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->download()->willReturn($this->getReadableStream('Hello World!')); + + $this->read('test')->shouldReturn('Hello World!'); + } + + function it_throws_file_not_found_while_reading_unexisting_file(Container $container) + { + $container->getObject('test')->willThrow($this->getBadResponseError(404)); + + $this->shouldThrow('Gaufrette\Exception\FileNotFound')->duringread('test'); + } + + function it_throws_storage_failure_while_reading(Container $container) + { + $container->getObject('test')->willThrow($this->getBadResponseError(400)); + + $this->shouldThrow('Gaufrette\Exception\StorageFailure')->duringread('test'); + } + + function it_writes_file(Container $container) + { + $container->createObject([ + 'name' => 'test', + 'content' => 'Hello World!', + ])->shouldBeCalled(); + + $this->write('test', 'Hello World!')->shouldNotThrow(); + } + + function it_throws_storage_failure_while_writing(Container $container) + { + $container->createObject([ + 'name' => 'test', + 'content' => 'Hello World!', + ])->willThrow($this->getBadResponseError(400)); + + $this->shouldThrow('Gaufrette\Exception\StorageFailure')->duringwrite('test', 'Hello World!'); + } + + function it_returns_true_if_key_exists(Container $container) + { + $container->objectExists('test')->willReturn(true); + + $this->exists('test')->shouldReturn(true); + } + + function it_returns_false_if_key_does_not_exist(Container $container) + { + $container->objectExists('test')->willReturn(false); + + $this->exists('test')->shouldReturn(false); + } + + function it_throws_storage_failure_while_checking_if_file_exists(Container $container) + { + $container->objectExists('test')->willThrow($this->getBadResponseError(400)); + + $this->shouldThrow('Gaufrette\Exception\StorageFailure')->duringexists('test'); + } + + function it_lists_objects(Container $container) + { + $client = new Client(); + $api = new Api(); + + $generate = function () use ($client, $api) { + for ($i = 0; $i < 3; $i++) { + $object = new StorageObject($client, $api); + $object->name = sprintf('object %d', $i + 1); + + yield $object; + } + }; + + $container->listObjects()->willReturn($generate()); + + $this->keys()->shouldReturn([ + 'object 1', + 'object 2', + 'object 3', + ]); + } + + function it_throws_storage_failure_while_listing_objects(Container $container) + { + $container->listObjects()->willThrow($this->getBadResponseError(400)); + + $this->shouldThrow('Gaufrette\Exception\StorageFailure')->duringkeys(); + } + + function it_lists_objects_with_prefix(Container $container) + { + $client = new Client(); + $api = new Api(); + + $generate = function () use ($client, $api) { + for ($i = 0; $i < 6; $i++) { + $object = new StorageObject($client, $api); + $object->name = sprintf('%sobject %d', $i < 3 ? 'prefixed ' : '', $i + 1); + + yield $object; + } + }; + + $container->listObjects()->willReturn($generate()); + + $this->listKeys('prefix')->shouldReturn([ + 'prefixed object 1', + 'prefixed object 2', + 'prefixed object 3', + ]); + } + + function it_throws_storage_failure_while_listing_objects_with_prefix(Container $container) + { + $container->listObjects()->willThrow($this->getBadResponseError(400)); + + $this->shouldThrow('Gaufrette\Exception\StorageFailure')->duringlistKeys('prefix'); + } + + function it_fetches_mtime(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->retrieve()->shouldBeCalled(); + $object->lastModified = 'Tue, 13 Jun 2017 22:02:34 GMT'; + + $this->mtime('test')->shouldReturn('1497391354'); + } + + function it_throws_file_not_found_exception_when_trying_to_fetch_the_mtime_of_an_unexisting_file(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->retrieve()->willThrow($this->getBadResponseError(404)); + + $this->shouldThrow('Gaufrette\Exception\FileNotFound')->duringmtime('test'); + } + + function it_throws_storage_failure_while_fetching_mtime(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->retrieve()->willThrow($this->getBadResponseError(400)); + + $this->shouldThrow('Gaufrette\Exception\StorageFailure')->duringmtime('test'); + } + + function it_deletes_file(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->delete()->shouldBeCalled(); + + $this->delete('test')->shouldNotThrow(); + } + + function it_throws_file_not_found_exception_when_trying_to_delete_an_unexisting_file(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->delete()->willThrow($this->getBadResponseError(404)); + + $this->shouldThrow('Gaufrette\Exception\FileNotFound')->duringdelete('test'); + } + + function it_throws_storage_failure_while_deleting(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->delete()->willThrow($this->getBadResponseError(400)); + + $this->shouldThrow('Gaufrette\Exception\StorageFailure')->duringdelete('test'); + } + + function it_renames_file(Container $container, StorageObject $source, StorageObject $dest) + { + $container->objectExists('source')->willReturn(true); + $container->objectExists('dest')->willReturn(false); + + $container->getObject('source')->willReturn($source); + $container->getObject('dest')->willReturn($dest); + $source->download()->willReturn($this->getReadableStream('Hello World!')); + $source->getMetadata()->willReturn(['meta' => 'data']); + $dest->resetMetadata(['meta' => 'data'])->shouldBeCalled(); + + $container->createObject([ + 'name' => 'dest', + 'content' => 'Hello World!', + ])->shouldBeCalled(); + + $source->delete()->shouldBeCalled(); + + $this->rename('source', 'dest')->shouldNotThrow(); + } + + function it_throws_file_not_found_exception_when_source_does_not_exist_during_rename(Container $container) + { + $container->objectExists('source')->willReturn(false); + + $this->shouldThrow('Gaufrette\Exception\FileNotFound')->duringrename('source', 'dest'); + } + + function it_throws_file_already_exists_when_dest_already_exists_during_rename(Container $container) + { + $container->objectExists('source')->willReturn(true); + $container->objectExists('dest')->willReturn(true); + + $this->shouldThrow('Gaufrette\Exception\FileAlreadyExists')->duringrename('source', 'dest'); + } + + function it_does_not_handle_directories() + { + $this->isDirectory('whatever')->shouldReturn(false); + } + + function it_returns_checksum(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->retrieve()->shouldBeCalled(); + $object->hash = '1234abcd'; + + $this->checksum('test')->shouldReturn('1234abcd'); + } + + function it_throws_file_not_found_exception_when_trying_to_get_the_checksum_of_an_unexisting_file(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->retrieve()->willThrow($this->getBadResponseError(404)); + + $this->shouldThrow('Gaufrette\Exception\FileNotFound')->duringchecksum('test'); + } + + function it_throws_storage_failure_while_fetching_checksum(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->retrieve()->willThrow($this->getBadResponseError(400)); + + $this->shouldThrow('Gaufrette\Exception\StorageFailure')->duringchecksum('test'); + } + + function it_fetches_metadata(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->getMetadata()->willReturn([ + 'foo' => 'bar', + ]); + + $this->getMetadata('test')->shouldReturn([ + 'foo' => 'bar', + ]); + } + + function it_throws_file_not_found_exception_when_trying_to_get_the_metadata_of_an_unexisting_file(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->getMetadata()->willThrow($this->getBadResponseError(404)); + + $this->shouldThrow('Gaufrette\Exception\FileNotFound')->duringgetMetadata('test'); + } + + function it_throws_storage_failure_while_fetching_metadata(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->getMetadata()->willThrow($this->getBadResponseError(400)); + + $this->shouldThrow('Gaufrette\Exception\StorageFailure')->duringgetMetadata('test'); + } + + function it_sets_metadata(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->resetMetadata([ + 'foo' => 'bar', + ])->shouldBeCalled(); + + $this->setMetadata('test', ['foo' => 'bar'])->shouldNotThrow(); + } + + function it_throws_file_not_found_exception_when_trying_to_set_the_metadata_of_an_unexisting_file(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->resetMetadata(['foo' => 'bar'])->willThrow($this->getBadResponseError(404)); + + $this->shouldThrow('Gaufrette\Exception\FileNotFound')->duringsetMetadata('test', ['foo' => 'bar']); + } + + function it_throws_storage_failure_while_setting_metadata(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->resetMetadata(['foo' => 'bar'])->willThrow($this->getBadResponseError(400)); + + $this->shouldThrow('Gaufrette\Exception\StorageFailure')->duringsetMetadata('test', ['foo' => 'bar']); + } + + function it_returns_mime_type(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->retrieve()->shouldBeCalled(); + $object->contentType = 'plain/text'; + + $this->mimeType('test')->shouldReturn('plain/text'); + } + + function it_throws_file_not_found_exception_when_trying_to_get_the_mime_type_of_an_unexisting_file(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->retrieve()->willThrow($this->getBadResponseError(404)); + + $this->shouldThrow('Gaufrette\Exception\FileNotFound')->duringmimeType('test'); + } + + function it_throws_storage_failure_while_fetching_mime_type(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->retrieve()->willThrow($this->getBadResponseError(400)); + + $this->shouldThrow('Gaufrette\Exception\StorageFailure')->duringmimeType('test'); + } + + function it_returns_size(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->retrieve()->shouldBeCalled(); + $object->contentLength = 42; + + $this->size('test')->shouldReturn(42); + } + + function it_throws_file_not_found_exception_when_trying_to_get_the_size_of_an_unexisting_file(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->retrieve()->willThrow($this->getBadResponseError(404)); + + $this->shouldThrow('Gaufrette\Exception\FileNotFound')->duringsize('test'); + } + + function it_throws_storage_failure_while_fetching_size(Container $container, StorageObject $object) + { + $container->getObject('test')->willReturn($object); + $object->retrieve()->willThrow($this->getBadResponseError(400)); + + $this->shouldThrow('Gaufrette\Exception\StorageFailure')->duringsize('test'); + } + + private function getReadableStream($content): StreamInterface + { + $stream = new BufferStream(); + $stream->write($content); + + return $stream; + } + + private function getBadResponseError(int $statusCode): BadResponseError + { + $error = new BadResponseError(); + $error->setResponse(new Response($statusCode)); + + return $error; + } +} diff --git a/spec/Gaufrette/FilesystemSpec.php b/spec/Gaufrette/FilesystemSpec.php index 4b0479ba6..caa861c4d 100644 --- a/spec/Gaufrette/FilesystemSpec.php +++ b/spec/Gaufrette/FilesystemSpec.php @@ -204,7 +204,7 @@ function it_does_not_delete_file_which_does_not_exist(Adapter $adapter) function it_fails_when_delete_is_not_successful(Adapter $adapter) { $adapter->exists('filename')->willReturn(true); - $adapter->delete('filename')->willThrow(StorageFailure::class); + $adapter->delete('filename')->willThrow(StorageFailure::unexpectedFailure('delete', [])); $this ->shouldThrow(StorageFailure::class) diff --git a/src/Gaufrette/Adapter.php b/src/Gaufrette/Adapter.php index 4c36310ca..5b22b777e 100644 --- a/src/Gaufrette/Adapter.php +++ b/src/Gaufrette/Adapter.php @@ -2,6 +2,7 @@ namespace Gaufrette; +use Gaufrette\Exception\FileAlreadyExists; use Gaufrette\Exception\FileNotFound; use Gaufrette\Exception\InvalidKey; use Gaufrette\Exception\StorageFailure; @@ -90,8 +91,9 @@ public function delete($key); * @param string $targetKey * * @throws FileNotFound - * @throws InvalidKey If $sourceKey and/or $targetKey are malformed - * @throws StorageFailure If the underlying storage fails (adapter should not leak exceptions) + * @throws FileAlreadyExists + * @throws InvalidKey If $sourceKey and/or $targetKey are malformed + * @throws StorageFailure If the underlying storage fails (adapter should not leak exceptions) */ public function rename($sourceKey, $targetKey); diff --git a/src/Gaufrette/Adapter/OpenCloud.php b/src/Gaufrette/Adapter/OpenCloud.php deleted file mode 100644 index 3877177bc..000000000 --- a/src/Gaufrette/Adapter/OpenCloud.php +++ /dev/null @@ -1,253 +0,0 @@ - - * @author Daniel Richter - */ -class OpenCloud implements Adapter, - ChecksumCalculator -{ - /** - * @var Service - */ - protected $objectStore; - - /** - * @var string - */ - protected $containerName; - - /** - * @var bool - */ - protected $createContainer; - - /** - * @var Container - */ - protected $container; - - /** - * @param Service $objectStore - * @param string $containerName The name of the container - * @param bool $createContainer Whether to create the container if it does not exist - */ - public function __construct(Service $objectStore, $containerName, $createContainer = false) - { - $this->objectStore = $objectStore; - $this->containerName = $containerName; - $this->createContainer = $createContainer; - } - - /** - * Returns an initialized container. - * - * @throws \RuntimeException - * - * @return Container - */ - protected function getContainer() - { - if ($this->container) { - return $this->container; - } - - try { - return $this->container = $this->objectStore->getContainer($this->containerName); - } catch (BadResponseException $e) { //OpenCloud lib does not wrap this exception - if (!$this->createContainer) { - throw new \RuntimeException(sprintf('Container "%s" does not exist.', $this->containerName)); - } - } - - if (!$container = $this->objectStore->createContainer($this->containerName)) { - throw new \RuntimeException(sprintf('Container "%s" could not be created.', $this->containerName)); - } - - return $this->container = $container; - } - - /** - * Reads the content of the file. - * - * @param string $key - * - * @return string|bool if cannot read content - */ - public function read($key) - { - if ($object = $this->tryGetObject($key)) { - return $object->getContent(); - } - - return false; - } - - /** - * Writes the given content into the file. - * - * @param string $key - * @param string $content - * - * @return int|bool The number of bytes that were written into the file - */ - public function write($key, $content) - { - try { - $this->getContainer()->uploadObject($key, $content); - } catch (CreateUpdateError $updateError) { - return false; - } - - return Util\Size::fromContent($content); - } - - /** - * Indicates whether the file exists. - * - * @param string $key - * - * @return bool - */ - public function exists($key) - { - try { - $exists = $this->getContainer()->getPartialObject($key) !== false; - } catch (BadResponseException $objFetchError) { - return false; - } - - return $exists; - } - - /** - * Returns an array of all keys (files and directories). - * - * @return array - */ - public function keys() - { - $objectList = $this->getContainer()->objectList(); - $keys = array(); - - while ($object = $objectList->next()) { - $keys[] = $object->getName(); - } - - sort($keys); - - return $keys; - } - - /** - * Returns the last modified time. - * - * @param string $key - * - * @return int|bool An UNIX like timestamp or false - */ - public function mtime($key) - { - if ($object = $this->tryGetObject($key)) { - return (new \DateTime($object->getLastModified()))->format('U'); - } - - return false; - } - - /** - * Deletes the file. - * - * @param string $key - * - * @return bool - */ - public function delete($key) - { - if (!$object = $this->tryGetObject($key)) { - return false; - } - - try { - $object->delete(); - } catch (DeleteError $deleteError) { - return false; - } - - return true; - } - - /** - * Renames a file. - * - * @param string $sourceKey - * @param string $targetKey - * - * @return bool - */ - public function rename($sourceKey, $targetKey) - { - if (false !== $this->write($targetKey, $this->read($sourceKey))) { - $this->delete($sourceKey); - - return true; - } - - return false; - } - - /** - * Check if key is directory. - * - * @param string $key - * - * @return bool - */ - public function isDirectory($key) - { - return false; - } - - /** - * Returns the checksum of the specified key. - * - * @param string $key - * - * @return string - */ - public function checksum($key) - { - if ($object = $this->tryGetObject($key)) { - return $object->getETag(); - } - - return false; - } - - /** - * @param string $key - * - * @return \OpenCloud\ObjectStore\Resource\DataObject|false - */ - protected function tryGetObject($key) - { - try { - return $this->getContainer()->getObject($key); - } catch (ObjectNotFoundException $objFetchError) { - return false; - } - } -} diff --git a/src/Gaufrette/Adapter/OpenStack.php b/src/Gaufrette/Adapter/OpenStack.php new file mode 100644 index 000000000..2b6c321f9 --- /dev/null +++ b/src/Gaufrette/Adapter/OpenStack.php @@ -0,0 +1,350 @@ + + * @author Daniel Richter + * @author Nicolas MURE + * + * @see http://docs.os.php-opencloud.com/en/latest/services/object-store/v1/objects.html + * @see http://refdocs.os.php-opencloud.com/OpenStack/OpenStack.html + */ +final class OpenStack implements Adapter, + ChecksumCalculator, + ListKeysAware, + MetadataSupporter, + MimeTypeProvider, + SizeCalculator +{ + /** + * @var Service + */ + private $objectStore; + + /** + * @var string + */ + private $containerName; + + /** + * @var Container + */ + private $container; + + /** + * @param Service $objectStore + * @param string $containerName The name of the container + */ + public function __construct(Service $objectStore, string $containerName) + { + $this->objectStore = $objectStore; + $this->containerName = $containerName; + } + + /** + * Returns an initialized container. + * + * @throws StorageFailure + * + * @return Container + */ + private function getContainer() + { + if ($this->container) { + return $this->container; + } + + try { + if ($this->objectStore->containerExists($this->containerName)) { + return $this->container = $this->objectStore->getContainer($this->containerName); + } + + throw new StorageFailure(sprintf('Container "%s" does not exist.', $this->containerName)); + } catch (BadResponseError $e) { + throw new StorageFailure( + sprintf('HTTP %d response received when checking the existence of the container "%s"', $e->getResponse()->getStatusCode(), $this->containerName), + $e->getCode(), + $e + ); + } + } + + /** + * {@inheritdoc} + */ + public function read($key) + { + try { + /** @var \Psr\Http\Message\StreamInterface $stream */ + // @WARNING: This could attempt to load a large amount of data into memory. + return (string) $this->getObject($key)->download(); + } catch (BadResponseError $e) { + if (404 === $e->getResponse()->getStatusCode()) { + throw new FileNotFound($key); + } + + throw StorageFailure::unexpectedFailure('read', ['key' => $key], $e); + } + } + + /** + * {@inheritdoc} + */ + public function write($key, $content) + { + try { + $this->getContainer()->createObject([ + 'name' => $key, + 'content' => $content, + ]); + } catch (BadResponseError $e) { + throw StorageFailure::unexpectedFailure( + 'write', + ['key' => $key, 'content' => $content], + $e + ); + } + } + + /** + * {@inheritdoc} + */ + public function exists($key) + { + try { + return $this->getContainer()->objectExists($key); + } catch (BadResponseError $e) { + throw StorageFailure::unexpectedFailure('exists', ['key' => $key], $e); + } + } + + /** + * {@inheritdoc} + */ + public function keys() + { + try { + return array_map(function (StorageObject $object) { + return $object->name; + }, iterator_to_array($this->getContainer()->listObjects())); + } catch (BadResponseError $e ) { + throw StorageFailure::unexpectedFailure('keys', [], $e); + } + } + + /** + * {@inheritdoc} + */ + public function listKeys($prefix = '') + { + try { + return array_filter($this->keys(), function ($key) use ($prefix) { + return 0 === strpos($key, $prefix); + }); + } catch (StorageFailure $e) { + throw StorageFailure::unexpectedFailure('listKeys', ['prefix' => $prefix], $e); + } + } + + /** + * {@inheritdoc} + */ + public function mtime($key) + { + try { + return (new \DateTime($this->retrieveObject($key)->lastModified))->format('U'); + } catch (BadResponseError $e) { + if (404 === $e->getResponse()->getStatusCode()) { + throw new FileNotFound($key); + } + + throw StorageFailure::unexpectedFailure('mtime', ['key' => $key], $e); + } + } + + /** + * {@inheritdoc} + */ + public function delete($key) + { + try { + $this->getObject($key)->delete(); + } catch (BadResponseError $e) { + if (404 === $e->getResponse()->getStatusCode()) { + throw new FileNotFound($key); + } + + throw StorageFailure::unexpectedFailure('delete', ['key' => $key], $e); + } + } + + /** + * {@inheritdoc} + */ + public function rename($sourceKey, $targetKey) + { + if (!$this->exists($sourceKey)) { + throw new FileNotFound($sourceKey); + } + + if($this->exists($targetKey)) { + throw new FileAlreadyExists($targetKey); + } + + try { + $this->write($targetKey, $this->read($sourceKey)); + + $metadata = $this->getMetadata($sourceKey); + if (!empty($metadata)) { + $this->setMetadata($targetKey, $metadata); + } + + $this->delete($sourceKey); + } catch (StorageFailure $e) { + throw StorageFailure::unexpectedFailure( + 'rename', + ['sourceKey' => $sourceKey, 'targetKey' => $targetKey], + $e + ); + } + } + + /** + * {@inheritdoc} + */ + public function isDirectory($key) + { + return false; + } + + /** + * {@inheritdoc} + */ + public function checksum($key) + { + try { + return $this->retrieveObject($key)->hash; + } catch (BadResponseError $e) { + if (404 === $e->getResponse()->getStatusCode()) { + throw new FileNotFound($key); + } + + throw StorageFailure::unexpectedFailure('checksum', ['key' => $key], $e); + } + } + + /** + * {@inheritdoc} + */ + public function getMetadata($key) + { + try { + return $this->getObject($key)->getMetadata(); + } catch (BadResponseError $e) { + if (404 === $e->getResponse()->getStatusCode()) { + throw new FileNotFound($key); + } + + throw StorageFailure::unexpectedFailure('getMetadata', [], $e); + } + } + + /** + * {@inheritdoc} + */ + public function setMetadata($key, $content) + { + try { + $this->getObject($key)->resetMetadata($content); + } catch (BadResponseError $e) { + if (404 === $e->getResponse()->getStatusCode()) { + throw new FileNotFound($key); + } + + throw StorageFailure::unexpectedFailure( + 'setMetadata', + ['key' => $key, 'content' => $content], + $e + ); + } + } + + /** + * {@inheritdoc} + */ + public function mimeType($key) + { + try { + return $this->retrieveObject($key)->contentType; + } catch (BadResponseError $e) { + if (404 === $e->getResponse()->getStatusCode()) { + throw new FileNotFound($key); + } + + throw StorageFailure::unexpectedFailure('mimeType', ['key' => $key], $e); + } + } + + /** + * {@inheritdoc} + */ + public function size($key) + { + try { + return $this->retrieveObject($key)->contentLength; + } catch (BadResponseError $e) { + if (404 === $e->getResponse()->getStatusCode()) { + throw new FileNotFound($key); + } + + throw StorageFailure::unexpectedFailure('size', ['key' => $key], $e); + } + } + + /** + * Shortcut to get an object from the container. + * This function will NOT perform an HTTP request. + * + * @param string $key + * + * @throws BadResponseError + * + * @return StorageObject + */ + private function getObject($key) + { + return $this->getContainer()->getObject($key); + } + + /** + * Shortcut to get an object from the container. + * The returned object will have its infos available (but not its content). + * This function WILL perform an HTTP request. + * + * @param string $key + * + * @throws BadResponseError + * + * @return StorageObject + */ + private function retrieveObject($key) + { + $object = $this->getObject($key); + $object->retrieve(); + + return $object; + } +} diff --git a/src/Gaufrette/Adapter/OpenStackCloudFiles/ObjectStoreFactory.php b/src/Gaufrette/Adapter/OpenStackCloudFiles/ObjectStoreFactory.php deleted file mode 100644 index 996ae2925..000000000 --- a/src/Gaufrette/Adapter/OpenStackCloudFiles/ObjectStoreFactory.php +++ /dev/null @@ -1,55 +0,0 @@ - - */ -class ObjectStoreFactory implements ObjectStoreFactoryInterface -{ - /** - * @var OpenStack - */ - protected $connection; - - /** - * @var string - */ - protected $region; - - /** - * @var string - */ - protected $urlType; - - /** - * @var string - */ - protected $objectStoreType; - - /** - * @param OpenStack $connection - * @param string $region - * @param string $urlType - * @param string $objectStoreType - */ - public function __construct(OpenStack $connection, $region, $urlType, $objectStoreType = 'cloudFiles') - { - $this->connection = $connection; - $this->region = $region; - $this->urlType = $urlType; - $this->objectStoreType = $objectStoreType; - } - - /** - * {@inheritdoc} - */ - public function getObjectStore() - { - return $this->connection->objectStoreService($this->objectStoreType, $this->region, $this->urlType); - } -} diff --git a/src/Gaufrette/Adapter/OpenStackCloudFiles/ObjectStoreFactoryInterface.php b/src/Gaufrette/Adapter/OpenStackCloudFiles/ObjectStoreFactoryInterface.php deleted file mode 100644 index 1129d8ea5..000000000 --- a/src/Gaufrette/Adapter/OpenStackCloudFiles/ObjectStoreFactoryInterface.php +++ /dev/null @@ -1,18 +0,0 @@ - - */ -interface ObjectStoreFactoryInterface -{ - /** - * @return Service - */ - public function getObjectStore(); -} diff --git a/src/Gaufrette/Filesystem.php b/src/Gaufrette/Filesystem.php index e185c73f0..89c5f3233 100644 --- a/src/Gaufrette/Filesystem.php +++ b/src/Gaufrette/Filesystem.php @@ -3,6 +3,7 @@ namespace Gaufrette; use Gaufrette\Adapter\ListKeysAware; +use Gaufrette\Exception as GaufretteException; /** * A filesystem is used to store and retrieve files. @@ -62,7 +63,15 @@ public function rename($sourceKey, $targetKey) throw new Exception\UnexpectedFile($targetKey); } - $this->adapter->rename($sourceKey, $targetKey); + try { + $this->adapter->rename($sourceKey, $targetKey); + } catch (\Exception $e) { + if ($e instanceof GaufretteException) { + throw $e; + } + + throw new \RuntimeException(sprintf('Could not rename the "%s" key to "%s".', $sourceKey, $targetKey)); + } if ($this->isFileInRegister($sourceKey)) { $this->fileRegister[$targetKey] = $this->fileRegister[$sourceKey]; @@ -119,8 +128,18 @@ public function delete($key) self::assertValidKey($key); $this->assertHasFile($key); - $this->adapter->delete($key); - $this->removeFromRegister($key); + try { + $this->adapter->delete($key); + $this->removeFromRegister($key); + + return true; + } catch (\Exception $e) { + if ($e instanceof GaufretteException) { + throw $e; + } + + throw new \RuntimeException(sprintf('Could not remove the "%s" key.', $key)); + } } /** diff --git a/tests/Gaufrette/Functional/Adapter/FunctionalTestCase.php b/tests/Gaufrette/Functional/Adapter/FunctionalTestCase.php index 56f4841af..021de5f88 100644 --- a/tests/Gaufrette/Functional/Adapter/FunctionalTestCase.php +++ b/tests/Gaufrette/Functional/Adapter/FunctionalTestCase.php @@ -62,7 +62,6 @@ public function shouldCheckIfFileExists() $this->assertTrue($this->filesystem->has('foo')); $this->assertFalse($this->filesystem->has('test/somefile')); - $this->assertFalse($this->filesystem->has('test/somefile')); } /** diff --git a/tests/Gaufrette/Functional/Adapter/OpenCloudTest.php b/tests/Gaufrette/Functional/Adapter/OpenCloudTest.php deleted file mode 100644 index ae459e4b6..000000000 --- a/tests/Gaufrette/Functional/Adapter/OpenCloudTest.php +++ /dev/null @@ -1,60 +0,0 @@ -markTestSkipped('Either RACKSPACE_USER, RACKSPACE_APIKEY and/or RACKSPACE_CONTAINER env vars are missing.'); - } - - $connection = new Rackspace( - 'https://identity.api.rackspacecloud.com/v2.0/', - [ - 'username' => $username, - 'apiKey' => $apiKey, - ], - [ - // Guzzle ships with outdated certs - // @see https://github.com/rackspace/php-opencloud/issues/727 - Rackspace::SSL_CERT_AUTHORITY => 'system', - Rackspace::CURL_OPTIONS => [ - CURLOPT_SSL_VERIFYPEER => true, - CURLOPT_SSL_VERIFYHOST => 2, - ], - ] - ); - - $this->container = uniqid($container); - $this->objectStore = $connection->objectStoreService('cloudFiles', 'IAD', 'publicURL'); - $this->objectStore->createContainer($this->container); - - $adapter = new OpenCloud($this->objectStore, $this->container); - $this->filesystem = new Filesystem($adapter); - } - - public function tearDown() - { - if ($this->filesystem === null) { - return; - } - - $this->objectStore->getContainer($this->container)->delete(true); - } -} diff --git a/tests/Gaufrette/Functional/Adapter/OpenStack/IdentityV2Test.php b/tests/Gaufrette/Functional/Adapter/OpenStack/IdentityV2Test.php new file mode 100644 index 000000000..124f4e74c --- /dev/null +++ b/tests/Gaufrette/Functional/Adapter/OpenStack/IdentityV2Test.php @@ -0,0 +1,55 @@ +markTestSkipped('Either RACKSPACE_USERNAME, RACKSPACE_PASSWORD, RACKSPACE_TENANT_ID and/or RACKSPACE_REGION env vars are missing.'); + } + + $authUrl = 'https://identity.api.rackspacecloud.com/v2.0/'; + + /* + * Rackspace uses OpenStack Identity v2 + * @see https://github.com/php-opencloud/openstack/issues/127 + */ + $this->container = uniqid('gaufretteci'); + $this->objectStore = (new OpenStack([ + 'username' => $username, + 'password' => $password, + 'tenantId' => $tenantId, + 'authUrl' => $authUrl, + 'region' => $region, + 'identityService' => IdentityService::factory( + new Client([ + 'base_uri' => $authUrl, + 'handler' => HandlerStack::create(), + ]) + ), + ])) + ->objectStoreV1([ + 'catalogName' => 'cloudFiles', + ]); + + $this->objectStore->createContainer([ + 'name' => $this->container, + ]); + $adapter = new OpenStackAdapter($this->objectStore, $this->container); + $this->filesystem = new Filesystem($adapter); + } +} diff --git a/tests/Gaufrette/Functional/Adapter/OpenStack/IdentityV3Test.php b/tests/Gaufrette/Functional/Adapter/OpenStack/IdentityV3Test.php new file mode 100644 index 000000000..183624ca0 --- /dev/null +++ b/tests/Gaufrette/Functional/Adapter/OpenStack/IdentityV3Test.php @@ -0,0 +1,40 @@ +markTestSkipped('Either IBMCLOUD_USERID, IBMCLOUD_PASSWORD, and/or IBMCLOUD_REGION env vars are missing.'); + } + + $authUrl = 'https://identity.open.softlayer.com/v3/'; + + $this->container = uniqid('gaufretteci'); + $this->objectStore = (new OpenStack([ + 'user' => [ + 'id' => $userId, + 'password' => $password, + ], + 'authUrl' => $authUrl, + 'region' => $region, + ])) + ->objectStoreV1(); + + $this->objectStore->createContainer([ + 'name' => $this->container, + ]); + $adapter = new OpenStackAdapter($this->objectStore, $this->container); + $this->filesystem = new Filesystem($adapter); + } +} diff --git a/tests/Gaufrette/Functional/Adapter/OpenStack/OpenStackTestCase.php b/tests/Gaufrette/Functional/Adapter/OpenStack/OpenStackTestCase.php new file mode 100644 index 000000000..3dcca1a90 --- /dev/null +++ b/tests/Gaufrette/Functional/Adapter/OpenStack/OpenStackTestCase.php @@ -0,0 +1,79 @@ +filesystem === null) { + return; + } + + // container must be empty to be deleted + array_map(function ($key) { + $this->filesystem->delete($key); + }, $this->filesystem->keys()); + + $this->objectStore->getContainer($this->container)->delete(); + } + + /** + * @test + * @group functional + */ + public function shouldGetChecksum() + { + $this->filesystem->write('foo', 'Some content'); + + $this->assertEquals(md5('Some content'), $this->filesystem->checksum('foo')); + } + + /** + * @test + * @group functional + */ + public function shouldGetSize() + { + $this->filesystem->write('foo', 'Some content'); + + $this->assertEquals(strlen('Some content'), $this->filesystem->size('foo')); + } + + /** + * @test + * @group functional + */ + public function shouldGetMimeType() + { + $this->filesystem->write('foo.txt', 'Some content'); + + $this->assertEquals('text/plain', $this->filesystem->mimeType('foo.txt')); + } + + /** + * @test + * @group functional + */ + public function shouldSetAndGetMetadata() + { + $this->filesystem->write('test.txt', 'Some content'); + $this->filesystem->getAdapter()->setMetadata('test.txt', [ + 'Some-Meta' => 'foo', + 'Custom-Stuff' => 'bar', + ]); + + $this->assertEquals([ + 'Some-Meta' => 'foo', + 'Custom-Stuff' => 'bar', + ], $this->filesystem->getAdapter()->getMetadata('test.txt')); + } +}