Got the new Openstack SDK working with Rackspace, and added my own support for the Rackspace-specific extensions for API key and CDN. #144

This commit is contained in:
Andy Heathershaw 2020-04-22 17:11:50 +01:00
parent 61c51fcd37
commit 7b2ea74a19
10 changed files with 272 additions and 23 deletions

View File

@ -132,21 +132,23 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource
protected function getClient() protected function getClient()
{ {
$openstackOptions = [ $authURL = $this->configuration->auth_url;
'authUrl' => $this->configuration->auth_url,
$options = [
'authUrl' => $authURL,
'username' => $this->configuration->username, 'username' => $this->configuration->username,
'password' => decrypt($this->configuration->password), 'password' => decrypt($this->configuration->password),
'tenantName' => $this->configuration->tenant_name, 'tenantName' => $this->configuration->tenant_name,
'region' => $this->configuration->service_region, 'region' => $this->configuration->service_region,
'identityService' => IdentityV2Service::factory( 'identityService' => IdentityV2Service::factory(
new Client([ new Client([
'base_uri' => TransportUtils::normalizeUrl( $this->configuration->auth_url), 'base_uri' => TransportUtils::normalizeUrl($authURL),
'handler' => HandlerStack::create(), 'handler' => HandlerStack::create(),
]) ])
) )
]; ];
return new OpenStack($openstackOptions); return new OpenStack($options);
} }
protected function getContainer() protected function getContainer()
@ -157,10 +159,15 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource
protected function getStorageService() protected function getStorageService()
{ {
return $this->getClient()->objectStoreV1([ return $this->getClient()->objectStoreV1([
'catalogName' => $this->configuration->service_name 'catalogName' => $this->getStorageServiceCatalogName()
]); ]);
} }
protected function getStorageServiceCatalogName()
{
return $this->configuration->service_name;
}
protected function getOriginalsFolder() protected function getOriginalsFolder()
{ {
return '_originals'; return '_originals';

View File

@ -3,24 +3,15 @@
namespace App\AlbumSources; namespace App\AlbumSources;
use App\Photo; 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 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. * Gets the name of this album source.
* @return string * @return string
@ -39,12 +30,14 @@ class RackspaceSource extends OpenStackSource
public function getUrlToPhoto(Photo $photo, $thumbnail = null) public function getUrlToPhoto(Photo $photo, $thumbnail = null)
{ {
$isCdnEnabled = false; $isCdnEnabled = false;
$cdnService = $this->getStorageService()->getCdnService(); $cdnService = $this->getCdnService();
$thisCdnContainer = null; $thisCdnContainer = null;
/** @var Container $cdnContainer */
foreach ($cdnService->listContainers() as $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; $isCdnEnabled = true;
$thisCdnContainer = $cdnContainer; $thisCdnContainer = $cdnContainer;
@ -53,9 +46,47 @@ class RackspaceSource extends OpenStackSource
if ($isCdnEnabled) 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); 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';
}
} }

View File

@ -0,0 +1,36 @@
<?php
namespace App\Services\Rackspace\Identity\v2;
use OpenStack\Common\Api\ApiInterface;
class Api implements ApiInterface
{
public function postTokenWithApiKey(): array
{
return [
'method' => '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',
],
],
];
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Services\Rackspace\Identity\v2;
use GuzzleHttp\ClientInterface;
use OpenStack\Common\Auth\IdentityService;
use OpenStack\Common\Service\AbstractService;
use OpenStack\Identity\v2\Models\Catalog;
use OpenStack\Identity\v2\Models\Token;
/**
* Represents the Rackspace Identity v2 service.
*
* @property Api $api
*/
class Service extends AbstractService implements IdentityService
{
public static function factory(ClientInterface $client): self
{
return new static($client, new Api());
}
public function authenticate(array $options = []): array
{
$definition = $this->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);
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Services\Rackspace\ObjectStoreCdn\v1;
use OpenStack\Common\Api\AbstractApi;
class Api extends AbstractApi
{
public function __construct()
{
$this->params = new Params();
}
public function getAccount(): array
{
return [
'method' => 'GET',
'path' => '',
'params' => [
'format' => $this->params->format()
]
];
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Services\Rackspace\ObjectStoreCdn\v1\Models;
use OpenStack\Common\Resource\OperatorResource;
class Container extends OperatorResource
{
/** @var bool */
public $cdn_enabled;
/** @var string */
public $cdn_ssl_uri;
/** @var string */
public $name;
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Services\Rackspace\ObjectStoreCdn\v1;
use OpenStack\Common\Api\AbstractParams;
class Params extends AbstractParams
{
public function format(): array
{
return [
'location' => self::QUERY,
'type' => self::STRING_TYPE,
'description' => 'Defines the format of the collection. Will always default to `json`.',
];
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Services\Rackspace\ObjectStoreCdn\v1;
use App\Services\Rackspace\ObjectStoreCdn\v1\Models\Container;
use OpenStack\Common\Service\AbstractService;
/**
* Represents the Rackspace Cloud Files CDN v1 service.
*
* @property Api $api
*/
class Service extends AbstractService
{
/**
* Retrieves a collection of CDN-enabled container resources in a generator format.
*
* @param array $options {@see Api::getAccount()}
* @param callable|null $mapFn allows a function to be mapped over each element in the collection
*/
public function listContainers(array $options = [], callable $mapFn = null): \Generator
{
$options = array_merge($options, ['format' => 'json']);
return $this->model(Container::class)->enumerate($this->api->getAccount(), $options, $mapFn);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Services\Rackspace;
use App\Services\Rackspace\ObjectStoreCdn\v1\Service as ObjectStoreCdnService;
use OpenStack\Common\Service\Builder;
use OpenStack\OpenStack;
class Rackspace extends OpenStack
{
/** @var Builder */
private $rsBuilder;
public function __construct(array $options = [], Builder $builder = null)
{
parent::__construct($options, $builder);
$this->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));
}
}

View File

@ -35,6 +35,10 @@ return [
'repo_owner' => 'aheathershaw' 'repo_owner' => 'aheathershaw'
], ],
'rackspace' => [
'authentication_url' => 'https://identity.api.rackspacecloud.com/v2.0'
],
'recaptcha' => [ 'recaptcha' => [
'verify_url' => 'https://www.google.com/recaptcha/api/siteverify' 'verify_url' => 'https://www.google.com/recaptcha/api/siteverify'
] ]