From 7b2ea74a198e175ff0f6b1af524f0d28d69b150f Mon Sep 17 00:00:00 2001 From: Andy Heathershaw Date: Wed, 22 Apr 2020 17:11:50 +0100 Subject: [PATCH] Got the new Openstack SDK working with Rackspace, and added my own support for the Rackspace-specific extensions for API key and CDN. #144 --- app/AlbumSources/OpenStackSource.php | 17 +++-- app/AlbumSources/RackspaceSource.php | 67 ++++++++++++++----- app/Services/Rackspace/Identity/v2/Api.php | 36 ++++++++++ .../Rackspace/Identity/v2/Service.php | 54 +++++++++++++++ .../Rackspace/ObjectStoreCdn/v1/Api.php | 24 +++++++ .../ObjectStoreCdn/v1/Models/Container.php | 17 +++++ .../Rackspace/ObjectStoreCdn/v1/Params.php | 17 +++++ .../Rackspace/ObjectStoreCdn/v1/Service.php | 27 ++++++++ app/Services/Rackspace/Rackspace.php | 32 +++++++++ config/services.php | 4 ++ 10 files changed, 272 insertions(+), 23 deletions(-) create mode 100644 app/Services/Rackspace/Identity/v2/Api.php create mode 100644 app/Services/Rackspace/Identity/v2/Service.php create mode 100644 app/Services/Rackspace/ObjectStoreCdn/v1/Api.php create mode 100644 app/Services/Rackspace/ObjectStoreCdn/v1/Models/Container.php create mode 100644 app/Services/Rackspace/ObjectStoreCdn/v1/Params.php create mode 100644 app/Services/Rackspace/ObjectStoreCdn/v1/Service.php create mode 100644 app/Services/Rackspace/Rackspace.php diff --git a/app/AlbumSources/OpenStackSource.php b/app/AlbumSources/OpenStackSource.php index 84af0f0..bde9709 100644 --- a/app/AlbumSources/OpenStackSource.php +++ b/app/AlbumSources/OpenStackSource.php @@ -132,21 +132,23 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource protected function getClient() { - $openstackOptions = [ - 'authUrl' => $this->configuration->auth_url, + $authURL = $this->configuration->auth_url; + + $options = [ + 'authUrl' => $authURL, 'username' => $this->configuration->username, 'password' => decrypt($this->configuration->password), 'tenantName' => $this->configuration->tenant_name, 'region' => $this->configuration->service_region, 'identityService' => IdentityV2Service::factory( new Client([ - 'base_uri' => TransportUtils::normalizeUrl( $this->configuration->auth_url), + 'base_uri' => TransportUtils::normalizeUrl($authURL), 'handler' => HandlerStack::create(), ]) ) ]; - return new OpenStack($openstackOptions); + return new OpenStack($options); } protected function getContainer() @@ -157,10 +159,15 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource protected function getStorageService() { return $this->getClient()->objectStoreV1([ - 'catalogName' => $this->configuration->service_name + 'catalogName' => $this->getStorageServiceCatalogName() ]); } + protected function getStorageServiceCatalogName() + { + return $this->configuration->service_name; + } + protected function getOriginalsFolder() { return '_originals'; diff --git a/app/AlbumSources/RackspaceSource.php b/app/AlbumSources/RackspaceSource.php index 62e7277..0cf22a0 100644 --- a/app/AlbumSources/RackspaceSource.php +++ b/app/AlbumSources/RackspaceSource.php @@ -3,24 +3,15 @@ namespace App\AlbumSources; use App\Photo; +use App\Services\Rackspace\Identity\v2\Service as RackspaceIdentityV2Service; +use App\Services\Rackspace\ObjectStoreCdn\v1\Models\Container; +use App\Services\Rackspace\Rackspace; +use GuzzleHttp\Client; +use GuzzleHttp\HandlerStack; +use OpenStack\Common\Transport\Utils as TransportUtils; class RackspaceSource extends OpenStackSource { - protected function getClient() - { - $endpoint = Rackspace::US_IDENTITY_ENDPOINT; - - if ($this->configuration->service_region == 'LON') - { - $endpoint = Rackspace::UK_IDENTITY_ENDPOINT; - } - - return new Rackspace($endpoint, [ - 'username' => $this->configuration->username, - 'apiKey' => decrypt($this->configuration->password) - ]); - } - /** * Gets the name of this album source. * @return string @@ -39,12 +30,14 @@ class RackspaceSource extends OpenStackSource public function getUrlToPhoto(Photo $photo, $thumbnail = null) { $isCdnEnabled = false; - $cdnService = $this->getStorageService()->getCdnService(); + $cdnService = $this->getCdnService(); + $thisCdnContainer = null; + /** @var Container $cdnContainer */ foreach ($cdnService->listContainers() as $cdnContainer) { - if ($cdnContainer->name == $this->configuration->container_name) + if ($cdnContainer->cdn_enabled && strtolower($cdnContainer->name) == strtolower($this->configuration->container_name)) { $isCdnEnabled = true; $thisCdnContainer = $cdnContainer; @@ -53,9 +46,47 @@ class RackspaceSource extends OpenStackSource if ($isCdnEnabled) { - return sprintf('%s/%s', $thisCdnContainer->getCdnSslUri(), $this->getPathToPhoto($photo, $thumbnail)); + return sprintf('%s/%s', $thisCdnContainer->cdn_ssl_uri, $this->getPathToPhoto($photo, $thumbnail)); } return parent::getPathToPhoto($photo, $thumbnail); } + + protected function getCdnService() + { + return $this->getClient()->objectStoreCdnV1(); + } + + protected function getClient() + { + $authURL = config('services.rackspace.authentication_url'); + + // Uncomment the commented out lines below and in the $options array to get a 'storage/logs/openstack.log' file + // with passed HTTP traffic + //$logger = new Logger('MyLog'); + //$logger->pushHandler(new StreamHandler(__DIR__ . '/../../storage/logs/openstack.log'), Logger::DEBUG); + + $options = [ + 'authUrl' => $authURL, + 'username' => $this->configuration->username, + 'apiKey' => decrypt($this->configuration->password), + 'region' => $this->configuration->service_region, + 'identityService' => RackspaceIdentityV2Service::factory( + new Client([ + 'base_uri' => TransportUtils::normalizeUrl($authURL), + 'handler' => HandlerStack::create(), + ]) + ), + //'debugLog' => true, + //'logger' => $logger, + //'messageFormatter' => new MessageFormatter('{req_body} - {res_body}') + ]; + + return new Rackspace($options); + } + + protected function getStorageServiceCatalogName() + { + return 'cloudFiles'; + } } \ No newline at end of file diff --git a/app/Services/Rackspace/Identity/v2/Api.php b/app/Services/Rackspace/Identity/v2/Api.php new file mode 100644 index 0000000..8242b5a --- /dev/null +++ b/app/Services/Rackspace/Identity/v2/Api.php @@ -0,0 +1,36 @@ + 'POST', + 'path' => 'tokens', + 'params' => [ + 'username' => [ + 'type' => 'string', + 'required' => true, + 'path' => 'auth.RAX-KSKEY:apiKeyCredentials', + ], + 'apiKey' => [ + 'type' => 'string', + 'required' => true, + 'path' => 'auth.RAX-KSKEY:apiKeyCredentials', + ], + 'tenantId' => [ + 'type' => 'string', + 'path' => 'auth', + ], + 'tenantName' => [ + 'type' => 'string', + 'path' => 'auth', + ], + ], + ]; + } +} \ No newline at end of file diff --git a/app/Services/Rackspace/Identity/v2/Service.php b/app/Services/Rackspace/Identity/v2/Service.php new file mode 100644 index 0000000..a867e10 --- /dev/null +++ b/app/Services/Rackspace/Identity/v2/Service.php @@ -0,0 +1,54 @@ +api->postTokenWithApiKey(); + + $response = $this->execute($definition, array_intersect_key($options, $definition['params'])); + + $token = $this->model(Token::class, $response); + + $serviceUrl = $this->model(Catalog::class, $response)->getServiceUrl( + $options['catalogName'], + $options['catalogType'], + $options['region'], + $options['urlType'] + ); + + return [$token, $serviceUrl]; + } + + /** + * Generates a new authentication token. + * + * @param array $options {@see \OpenStack\Identity\v2\Api::postToken} + * + * @return Token + */ + public function generateToken(array $options = []): Token + { + $response = $this->execute($this->api->postTokenWithApiKey(), $options); + + return $this->model(Token::class, $response); + } +} \ No newline at end of file diff --git a/app/Services/Rackspace/ObjectStoreCdn/v1/Api.php b/app/Services/Rackspace/ObjectStoreCdn/v1/Api.php new file mode 100644 index 0000000..1817db5 --- /dev/null +++ b/app/Services/Rackspace/ObjectStoreCdn/v1/Api.php @@ -0,0 +1,24 @@ +params = new Params(); + } + + public function getAccount(): array + { + return [ + 'method' => 'GET', + 'path' => '', + 'params' => [ + 'format' => $this->params->format() + ] + ]; + } +} \ No newline at end of file diff --git a/app/Services/Rackspace/ObjectStoreCdn/v1/Models/Container.php b/app/Services/Rackspace/ObjectStoreCdn/v1/Models/Container.php new file mode 100644 index 0000000..2fb3463 --- /dev/null +++ b/app/Services/Rackspace/ObjectStoreCdn/v1/Models/Container.php @@ -0,0 +1,17 @@ + self::QUERY, + 'type' => self::STRING_TYPE, + 'description' => 'Defines the format of the collection. Will always default to `json`.', + ]; + } +} \ No newline at end of file diff --git a/app/Services/Rackspace/ObjectStoreCdn/v1/Service.php b/app/Services/Rackspace/ObjectStoreCdn/v1/Service.php new file mode 100644 index 0000000..e5017e9 --- /dev/null +++ b/app/Services/Rackspace/ObjectStoreCdn/v1/Service.php @@ -0,0 +1,27 @@ + 'json']); + + return $this->model(Container::class)->enumerate($this->api->getAccount(), $options, $mapFn); + } +} \ No newline at end of file diff --git a/app/Services/Rackspace/Rackspace.php b/app/Services/Rackspace/Rackspace.php new file mode 100644 index 0000000..e4245b5 --- /dev/null +++ b/app/Services/Rackspace/Rackspace.php @@ -0,0 +1,32 @@ +rsBuilder = $builder ?: new Builder($options, 'App\Services\Rackspace'); + } + + /** + * Creates a new Object Store (Rackspace CDN) v1 service. + * + * @param array $options options that will be used in configuring the service + */ + public function objectStoreCdnV1(array $options = []): ObjectStoreCdnService + { + $defaults = ['catalogName' => 'cloudFilesCDN', 'catalogType' => 'rax:object-cdn']; + + return $this->rsBuilder->createService('ObjectStoreCdn\\v1', array_merge($defaults, $options)); + } +} \ No newline at end of file diff --git a/config/services.php b/config/services.php index 7edaecd..726cfc4 100644 --- a/config/services.php +++ b/config/services.php @@ -35,6 +35,10 @@ return [ 'repo_owner' => 'aheathershaw' ], + 'rackspace' => [ + 'authentication_url' => 'https://identity.api.rackspacecloud.com/v2.0' + ], + 'recaptcha' => [ 'verify_url' => 'https://www.google.com/recaptcha/api/siteverify' ]