Skip to content

Commit

Permalink
add errorVerbosity
Browse files Browse the repository at this point in the history
  • Loading branch information
k0ka committed Feb 5, 2024
1 parent c32ab23 commit 8d3678a
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 82 deletions.
32 changes: 24 additions & 8 deletions doc/setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,47 @@ Only the ``authUrl`` is mandatory to create the client. But you will have to pro
credentials to each service you create. So it is recommended to provide them when creating the client which
would propagate these options to each service.

Authenticate
------------

There are different ways to provide the authentication credentials. See the :doc:`services/identity/v3/tokens`
section for the full list of options. You should provide credentials to the ``OpenStack`` constructor as an array
the same way you provide options to ``generateToken`` method of the ``Identity`` service.

Authenticate with username
--------------------------
By username
~~~~~~~~~~~

The most common way to authenticate is using the username and password of the user. You should also provide the Domain ID
as usernames will not be unique across an entire OpenStack installation

.. sample:: Setup/username.php

Authenticate with user ID
-------------------------
By user ID
~~~~~~~~~~

.. sample:: Setup/user_id.php

Authenticate application credential ID
--------------------------------------
By application credential ID
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. sample:: Setup/application_credential_id.php

Authenticate using token ID
---------------------------
By token ID
~~~~~~~~~~~

If you already have a valid token, you can use it to authenticate.

.. sample:: Setup/token_id.php

Other options
-------------

For production environments it is recommended to decrease error reporting not to expose sensitive information. It can be done
by setting the ``errorVerbosity`` key to ``0`` in the options array. It is set to 2 by default.

.. code-block:: php
$openstack = new OpenStack\OpenStack([
'errorVerbosity' => 0,
// other options
]);
2 changes: 1 addition & 1 deletion src/Common/Auth/IdentityService.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface IdentityService
/**
* Authenticates and retrieves back a token and catalog.
*
* @return array The FIRST key is {@see Token} instance, the SECOND key is a {@see Catalog} instance
* @return array{0: \OpenStack\Common\Auth\Token, 1: string} The FIRST key is {@see Token} instance, the SECOND key is a URL of the service
*/
public function authenticate(array $options): array;
}
55 changes: 33 additions & 22 deletions src/Common/Error/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private function header(string $name): string
*/
private function linkIsValid(string $link): bool
{
$link = $this->docDomain.$link;
$link = $this->docDomain . $link;

try {
return $this->client->request('HEAD', $link)->getStatusCode() < 400;
Expand All @@ -66,39 +66,50 @@ private function linkIsValid(string $link): bool
/**
* @codeCoverageIgnore
*/
public function str(MessageInterface $message): string
public function str(MessageInterface $message, int $verbosity = 0): string
{
if ($message instanceof RequestInterface) {
$msg = trim($message->getMethod().' '
.$message->getRequestTarget())
.' HTTP/'.$message->getProtocolVersion();
$msg = trim($message->getMethod() . ' ' . $message->getRequestTarget());
$msg .= ' HTTP/' . $message->getProtocolVersion();
if (!$message->hasHeader('host')) {
$msg .= "\r\nHost: ".$message->getUri()->getHost();
$msg .= "\r\nHost: " . $message->getUri()->getHost();
}
} elseif ($message instanceof ResponseInterface) {
$msg = 'HTTP/'.$message->getProtocolVersion().' '
.$message->getStatusCode().' '
.$message->getReasonPhrase();
} else {
if ($message instanceof ResponseInterface) {
$msg = 'HTTP/' . $message->getProtocolVersion() . ' '
. $message->getStatusCode() . ' '
. $message->getReasonPhrase();
} else {
throw new \InvalidArgumentException('Unknown message type');
}
}

if ($verbosity < 1) {
return $msg;
}

foreach ($message->getHeaders() as $name => $values) {
$msg .= "\r\n{$name}: ".implode(', ', $values);
$msg .= "\r\n{$name}: " . implode(', ', $values);
}

if ($verbosity < 2) {
return $msg;
}

if (ini_get('memory_limit') < 0 || $message->getBody()->getSize() < ini_get('memory_limit')) {
$msg .= "\r\n\r\n".$message->getBody();
$msg .= "\r\n\r\n" . $message->getBody();
}

return $msg;
return trim($msg);
}

/**
* Helper method responsible for constructing and returning {@see BadResponseError} exceptions.
*
* @param RequestInterface $request The faulty request
* @param RequestInterface $request The faulty request
* @param ResponseInterface $response The error-filled response
*/
public function httpError(RequestInterface $request, ResponseInterface $response): BadResponseError
public function httpError(RequestInterface $request, ResponseInterface $response, int $verbosity = 0): BadResponseError
{
$message = $this->header('HTTP Error');

Expand All @@ -109,16 +120,16 @@ public function httpError(RequestInterface $request, ResponseInterface $response
);

$message .= $this->header('Request');
$message .= trim($this->str($request)).PHP_EOL.PHP_EOL;
$message .= $this->str($request, $verbosity) . PHP_EOL . PHP_EOL;

$message .= $this->header('Response');
$message .= trim($this->str($response)).PHP_EOL.PHP_EOL;
$message .= $this->str($response, $verbosity) . PHP_EOL . PHP_EOL;

$message .= $this->header('Further information');
$message .= $this->getStatusCodeMessage($response->getStatusCode());

$message .= 'Visit http://docs.php-opencloud.com/en/latest/http-codes for more information about debugging '
.'HTTP status codes, or file a support issue on https://github.com/php-opencloud/openstack/issues.';
. 'HTTP status codes, or file a support issue on https://github.com/php-opencloud/openstack/issues.';

$e = new BadResponseError($message);
$e->setRequest($request);
Expand All @@ -142,9 +153,9 @@ private function getStatusCodeMessage(int $statusCode): string
/**
* Helper method responsible for constructing and returning {@see UserInputError} exceptions.
*
* @param string $expectedType The type that was expected from the user
* @param mixed $userValue The incorrect value the user actually provided
* @param string|null $furtherLink a link to further information if necessary (optional)
* @param string $expectedType The type that was expected from the user
* @param mixed $userValue The incorrect value the user actually provided
* @param string|null $furtherLink a link to further information if necessary (optional)
*/
public function userInputError(string $expectedType, $userValue, string $furtherLink = null): UserInputError
{
Expand All @@ -159,7 +170,7 @@ public function userInputError(string $expectedType, $userValue, string $further
$message .= 'Please ensure that the value adheres to the expectation above. ';

if ($furtherLink && $this->linkIsValid($furtherLink)) {
$message .= sprintf('Visit %s for more information about input arguments. ', $this->docDomain.$furtherLink);
$message .= sprintf('Visit %s for more information about input arguments. ', $this->docDomain . $furtherLink);
}

$message .= 'If you run into trouble, please open a support issue on https://github.com/php-opencloud/openstack/issues.';
Expand Down
39 changes: 8 additions & 31 deletions src/Common/Service/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class Builder
private $defaults = ['urlType' => 'publicURL'];

/**
* @param array $globalOptions options that will be applied to every service created by this builder.
* @param array $globalOptions options that will be applied to every service created by this builder.
* Eventually they will be merged (and if necessary overridden) by the
* service-specific options passed in
* @param string $rootNamespace API classes' root namespace
Expand All @@ -50,8 +50,8 @@ public function __construct(array $globalOptions = [], $rootNamespace = 'OpenSta

private function getClasses($namespace)
{
$namespace = $this->rootNamespace.'\\'.$namespace;
$classes = [$namespace.'\\Api', $namespace.'\\Service'];
$namespace = $this->rootNamespace . '\\' . $namespace;
$classes = [$namespace . '\\Api', $namespace . '\\Service'];

foreach ($classes as $class) {
if (!class_exists($class)) {
Expand All @@ -68,8 +68,8 @@ private function getClasses($namespace)
* directly - this setup includes the configuration of the HTTP client's base URL, and the
* attachment of an authentication handler.
*
* @param string $namespace The namespace of the service
* @param array $serviceOptions The service-specific options to use
* @param string $namespace The namespace of the service
* @param array $serviceOptions The service-specific options to use
*/
public function createService(string $namespace, array $serviceOptions = []): ServiceInterface
{
Expand All @@ -88,33 +88,18 @@ private function stockHttpClient(array &$options, string $serviceName): void
if (!isset($options['httpClient']) || !($options['httpClient'] instanceof ClientInterface)) {
if (false !== stripos($serviceName, 'identity')) {
$baseUrl = $options['authUrl'];
$stack = $this->getStack($options['authHandler']);
$token = null;
} else {
[$token, $baseUrl] = $options['identityService']->authenticate($options);
$stack = $this->getStack($options['authHandler'], $token);
[$token, $baseUrl] = $options['identityService']->authenticate($options);
}

$stack = HandlerStackFactory::createWithOptions(array_merge($options, ['token' => $token]));
$microVersion = $options['microVersion'] ?? null;

$this->addDebugMiddleware($options, $stack);

$options['httpClient'] = $this->httpClient($baseUrl, $stack, $options['catalogType'], $microVersion);
}
}

/**
* @codeCoverageIgnore
*/
private function addDebugMiddleware(array $options, HandlerStack &$stack): void
{
if (!empty($options['debugLog'])
&& !empty($options['logger'])
&& !empty($options['messageFormatter'])
) {
$stack->push(GuzzleMiddleware::log($options['logger'], $options['messageFormatter']));
}
}

/**
* @codeCoverageIgnore
*/
Expand All @@ -127,14 +112,6 @@ private function stockAuthHandler(array &$options): void
}
}

private function getStack(callable $authHandler, Token $token = null): HandlerStack
{
$stack = HandlerStackFactory::create();
$stack->push(Middleware::authHandler($authHandler, $token));

return $stack;
}

private function httpClient(string $baseUrl, HandlerStack $stack, string $serviceType = null, string $microVersion = null): ClientInterface
{
$clientOptions = [
Expand Down
3 changes: 3 additions & 0 deletions src/Common/Transport/HandlerStack.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
*/
class HandlerStack
{
/**
* @deprecated use \OpenStack\Common\Transport\HandlerStackFactory::createWithOptions instead
*/
public static function create(callable $handler = null): \GuzzleHttp\HandlerStack
{
return HandlerStackFactory::create($handler);
Expand Down
38 changes: 38 additions & 0 deletions src/Common/Transport/HandlerStackFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
namespace OpenStack\Common\Transport;

use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware as GuzzleMiddleware;
use GuzzleHttp\Utils;

class HandlerStackFactory
{
/**
* @deprecated use \OpenStack\Common\Transport\HandlerStackFactory::createWithOptions instead
*/
public static function create(callable $handler = null): HandlerStack
{
$stack = new HandlerStack($handler ?: Utils::chooseHandler());
Expand All @@ -17,4 +21,38 @@ public static function create(callable $handler = null): HandlerStack

return $stack;
}

/**
* Creates a new HandlerStack with the given options
*
* @param array{
* handler: callable,
* authHandler: callable,
* token: \OpenStack\Common\Auth\Token,
* errorVerbosity: int,
* debugLog: bool,
* logger: \Psr\Log\LoggerInterface,
* messageFormatter: \GuzzleHttp\MessageFormatter
* } $options
*/
public static function createWithOptions(array $options): HandlerStack
{
$stack = new HandlerStack($options['handler'] ?? Utils::chooseHandler());
$stack->push(Middleware::httpErrors($options['errorVerbosity'] ?? 0), 'http_errors');
$stack->push(GuzzleMiddleware::prepareBody(), 'prepare_body');

if (!empty($options['authHandler'])){
$stack->push(Middleware::authHandler($options['authHandler'], $options['token'] ?? null));
}

if (!empty($options['debugLog'])
&& !empty($options['logger'])
&& !empty($options['messageFormatter'])
) {
$logMiddleware = GuzzleMiddleware::log($options['logger'], $options['messageFormatter']);
$stack->push($logMiddleware, 'logger');
}

return $stack;
}
}
10 changes: 5 additions & 5 deletions src/Common/Transport/Middleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@

final class Middleware
{
public static function httpErrors(): callable
public static function httpErrors(int $verbosity = 0): callable
{
return function (callable $handler) {
return function ($request, array $options) use ($handler) {
return function (callable $handler) use ($verbosity) {
return function ($request, array $options) use ($handler, $verbosity) {
return $handler($request, $options)->then(
function (ResponseInterface $response) use ($request) {
function (ResponseInterface $response) use ($request, $verbosity) {
if ($response->getStatusCode() < 400) {
return $response;
}
throw (new Builder())->httpError($request, $response);
throw (new Builder())->httpError($request, $response, $verbosity);
}
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/Identity/v3/Service.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function authenticate(array $options): array
$name = $options['catalogName'];
$type = $options['catalogType'];
$region = $options['region'];
$interface = isset($options['interface']) ? $options['interface'] : Enum::INTERFACE_PUBLIC;
$interface = $options['interface'] ?? Enum::INTERFACE_PUBLIC;

if ($baseUrl = $token->catalog->getServiceUrl($name, $type, $region, $interface)) {
return [$token, $baseUrl];
Expand Down
13 changes: 4 additions & 9 deletions src/OpenStack.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ class OpenStack
*/
public function __construct(array $options = [], Builder $builder = null)
{
$defaults = ['errorVerbosity' => 2];
$options = array_merge($defaults, $options);

if (!isset($options['identityService'])) {
$options['identityService'] = $this->getDefaultIdentityService($options);
}
Expand All @@ -49,15 +52,7 @@ private function getDefaultIdentityService(array $options): Service
throw new \InvalidArgumentException("'authUrl' is a required option");
}

$stack = HandlerStackFactory::create();

if (!empty($options['debugLog'])
&& !empty($options['logger'])
&& !empty($options['messageFormatter'])
) {
$logMiddleware = GuzzleMiddleware::log($options['logger'], $options['messageFormatter']);
$stack->push($logMiddleware, 'logger');
}
$stack = HandlerStackFactory::createWithOptions(array_merge($options, ['token' => null]));

$clientOptions = [
'base_uri' => Utils::normalizeUrl($options['authUrl']),
Expand Down
Loading

0 comments on commit 8d3678a

Please sign in to comment.