migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Api;
abstract class AbstractApi implements ApiInterface
{
protected $params;
protected function isRequired(array $param): array
{
return array_merge($param, ['required' => true]);
}
protected function notRequired(array $param): array
{
return array_merge($param, ['required' => false]);
}
protected function query(array $param): array
{
return array_merge($param, ['location' => AbstractParams::QUERY]);
}
protected function url(array $param): array
{
return array_merge($param, ['location' => AbstractParams::URL]);
}
public function documented(array $param): array
{
return array_merge($param, ['required' => true]);
}
}
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Api;
abstract class AbstractParams
{
// locations
const QUERY = 'query';
const HEADER = 'header';
const URL = 'url';
const JSON = 'json';
const RAW = 'raw';
// types
const STRING_TYPE = 'string';
const BOOL_TYPE = 'boolean';
const BOOLEAN_TYPE = self::BOOL_TYPE;
const OBJECT_TYPE = 'object';
const ARRAY_TYPE = 'array';
const NULL_TYPE = 'NULL';
const INT_TYPE = 'integer';
const INTEGER_TYPE = self::INT_TYPE;
public static function isSupportedLocation(string $val): bool
{
return in_array($val, [self::QUERY, self::HEADER, self::URL, self::JSON, self::RAW]);
}
public function limit(): array
{
return [
'type' => self::INT_TYPE,
'location' => 'query',
'description' => <<<DESC
This will limit the total amount of elements returned in a list up to the number specified. For example, specifying a
limit of 10 will return 10 elements, regardless of the actual count.
DESC
];
}
public function marker(): array
{
return [
'type' => 'string',
'location' => 'query',
'description' => <<<DESC
Specifying a marker will begin the list from the value specified. Elements will have a particular attribute that
identifies them, such as a name or ID. The marker value will search for an element whose identifying attribute matches
the marker value, and begin the list from there.
DESC
];
}
public function id(string $type): array
{
return [
'description' => sprintf('The unique ID, or identifier, for the %s', $type),
'type' => self::STRING_TYPE,
'location' => self::JSON,
];
}
public function idPath(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::URL,
'description' => 'The unique ID of the resource',
];
}
public function name(string $resource): array
{
return [
'description' => sprintf('The name of the %s', $resource),
'type' => self::STRING_TYPE,
'location' => self::JSON,
];
}
public function sortDir(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::QUERY,
'description' => 'Sorts by one or more sets of attribute and sort direction combinations.',
'enum' => ['asc', 'desc'],
];
}
public function sortKey(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::QUERY,
'description' => 'Sorts by one or more sets of attribute and sort direction combinations.',
];
}
public function allTenants(): array
{
return [
'type' => self::BOOL_TYPE,
'location' => self::QUERY,
'sentAs' => 'all_tenants',
'description' => '(Admin only) Set this to true to pull volume information from all tenants.',
];
}
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Api;
/**
* All classes which implement this interface are a data representation of a remote OpenStack API.
* They do not execute functionality, but instead return data for each API operation for other parts
* of the SDK to use. Usually, the data is injected into {@see OpenStack\Common\Api\Operation} objects.
* The operation is then serialized into a {@see GuzzleHttp\Message\Request} and sent to the API.
*
* The reason for storing all the API-specific data is to decouple service information from client
* HTTP functionality. Too often it is mixed all across different layers, leading to duplication and
* no separation of concerns. The choice was made for storage in PHP classes, rather than YAML or JSON
* syntax, due to performance concerns.
*/
interface ApiInterface
{
}
@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Api;
/**
* This class represents an OpenStack API operation. It encapsulates most aspects of the REST operation: its HTTP
* method, the URL path, its top-level JSON key, and all of its {@see Parameter} objects.
*
* An operation not only represents a remote operation, but it also provides the mechanism for executing it
* over HTTP. To do this, it uses a {@see ClientInterface} that allows a {@see GuzzleHttp\Message\Request}
* to be created from the user values provided. Once this request is assembled, it is then sent to the
* remote API and the response is returned to whoever first invoked the Operation class.
*/
class Operation
{
/** @var string The HTTP method */
private $method;
/** @var string The URL path */
private $path;
/** @var string The top-level JSON key */
private $jsonKey;
/** @var []Parameter The parameters of this operation */
private $params;
/**
* @param array $definition The data definition (in array form) that will populate this
* operation. Usually this is retrieved from an {@see ApiInterface}
* object method.
*/
public function __construct(array $definition)
{
$this->method = $definition['method'];
$this->path = $definition['path'];
if (isset($definition['jsonKey'])) {
$this->jsonKey = $definition['jsonKey'];
}
$this->params = self::toParamArray($definition['params']);
}
public function getPath(): string
{
return $this->path;
}
public function getMethod(): string
{
return $this->method;
}
/**
* Indicates whether this operation supports a parameter.
*
* @param $key The name of a parameter
*/
public function hasParam(string $key): bool
{
return isset($this->params[$key]);
}
/**
* @param $name
*
* @return Parameter
*/
public function getParam(string $name)
{
return isset($this->params[$name]) ? $this->params[$name] : null;
}
public function getJsonKey(): string
{
return $this->jsonKey ?: '';
}
/**
* A convenience method that will take a generic array of data and convert it into an array of
* {@see Parameter} objects.
*
* @param array $data A generic data array
*/
public static function toParamArray(array $data): array
{
$params = [];
foreach ($data as $name => $param) {
$params[$name] = new Parameter($param + ['name' => $name]);
}
return $params;
}
/**
* This method will validate all of the user-provided values and throw an exception if any
* failures are detected. This is useful for basic sanity-checking before a request is
* serialized and sent to the API.
*
* @param array $userValues The user-defined values
*
* @return bool TRUE if validation passes
*
* @throws \Exception If validate fails
*/
public function validate(array $userValues): bool
{
foreach ($this->params as $paramName => $param) {
if (array_key_exists($paramName, $userValues)) {
$param->validate($userValues[$paramName]);
} elseif ($param->isRequired()) {
throw new \Exception(sprintf('"%s" is a required option, but it was not provided', $paramName));
}
}
return true;
}
}
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Api;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Promise\PromiseInterface;
use OpenStack\Common\Resource\ResourceInterface;
use Psr\Http\Message\ResponseInterface;
/**
* An operator is any resource or service that can invoke and send REST operations. In other words, it
* is any class that can send requests and receive responses with a HTTP client. To do this
* it needs two things: a {@see ClientInterface} for handling HTTP transactions and an {@see ApiInterface}
* for handling how operations are created.
*/
interface OperatorInterface
{
/**
* @param ClientInterface $client The HTTP client responsible for handling HTTP transactions
* @param ApiInterface $api The data API class that dictates how REST operations are structured
*/
public function __construct(ClientInterface $client, ApiInterface $api);
/**
* A convenience method that assembles an operation and sends it to the remote API.
*
* @param array $definition The data that dictates how the operation works
* @param array $userValues The user-defined values that populate the request
*/
public function execute(array $definition, array $userValues = []): ResponseInterface;
/**
* A convenience method that assembles an operation and asynchronously sends it to the remote API.
*
* @param array $definition The data that dictates how the operation works
* @param array $userValues The user-defined values that populate the request
*/
public function executeAsync(array $definition, array $userValues = []): PromiseInterface;
/**
* Retrieves a populated Operation according to the definition and values provided. A
* HTTP client is also injected into the object to allow it to communicate with the remote API.
*
* @param array $definition The data that dictates how the operation works
*/
public function getOperation(array $definition): Operation;
/**
* @param string $class the name of the model class
* @param mixed $data either a {@see ResponseInterface} or data array that will populate the newly
* created model class
*/
public function model(string $class, $data = null): ResourceInterface;
}
@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Api;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;
use OpenStack\Common\Resource\ResourceInterface;
use OpenStack\Common\Transport\RequestSerializer;
use OpenStack\Common\Transport\Utils;
use Psr\Http\Message\ResponseInterface;
trait OperatorTrait
{
/** @var ClientInterface */
protected $client;
/** @var ApiInterface */
protected $api;
/**
* {@inheritdoc}
*/
public function __construct(ClientInterface $client, ApiInterface $api)
{
$this->client = $client;
$this->api = $api;
}
/**
* Magic method for dictating how objects are rendered when var_dump is called.
* For the benefit of users, extremely verbose and heavy properties (such as HTTP clients) are
* removed to provide easier access to normal state, such as resource attributes.
*
* @codeCoverageIgnore
*
* @return array
*/
public function __debugInfo()
{
$excludedVars = ['client', 'errorBuilder', 'api'];
$output = [];
foreach (get_object_vars($this) as $key => $val) {
if (!in_array($key, $excludedVars)) {
$output[$key] = $val;
}
}
return $output;
}
/**
* Magic method which intercepts async calls, finds the sequential version, and wraps it in a
* {@see Promise} object. In order for this to happen, the called methods need to be in the
* following format: `createAsync`, where `create` is the sequential method being wrapped.
*
* @param $methodName the name of the method being invoked
* @param $args the arguments to be passed to the sequential method
*
* @throws \RuntimeException If method does not exist
*
* @return Promise
*/
public function __call($methodName, $args)
{
$e = function ($name) {
return new \RuntimeException(sprintf('%s::%s is not defined', get_class($this), $name));
};
if ('Async' === substr($methodName, -5)) {
$realMethod = substr($methodName, 0, -5);
if (!method_exists($this, $realMethod)) {
throw $e($realMethod);
}
$promise = new Promise(
function () use (&$promise, $realMethod, $args) {
$value = call_user_func_array([$this, $realMethod], $args);
$promise->resolve($value);
}
);
return $promise;
}
throw $e($methodName);
}
/**
* {@inheritdoc}
*/
public function getOperation(array $definition): Operation
{
return new Operation($definition);
}
/**
* @return mixed
*
* @throws \Exception
*/
protected function sendRequest(Operation $operation, array $userValues = [], bool $async = false)
{
$operation->validate($userValues);
$options = (new RequestSerializer())->serializeOptions($operation, $userValues);
$method = $async ? 'requestAsync' : 'request';
$uri = Utils::uri_template($operation->getPath(), $userValues);
if (array_key_exists('requestOptions', $userValues)) {
$options += $userValues['requestOptions'];
}
return $this->client->$method($operation->getMethod(), $uri, $options);
}
/**
* {@inheritdoc}
*/
public function execute(array $definition, array $userValues = []): ResponseInterface
{
return $this->sendRequest($this->getOperation($definition), $userValues);
}
/**
* {@inheritdoc}
*/
public function executeAsync(array $definition, array $userValues = []): PromiseInterface
{
return $this->sendRequest($this->getOperation($definition), $userValues, true);
}
/**
* {@inheritdoc}
*/
public function model(string $class, $data = null): ResourceInterface
{
$model = new $class($this->client, $this->api);
// @codeCoverageIgnoreStart
if (!$model instanceof ResourceInterface) {
throw new \RuntimeException(sprintf('%s does not implement %s', $class, ResourceInterface::class));
}
// @codeCoverageIgnoreEnd
if ($data instanceof ResponseInterface) {
$model->populateFromResponse($data);
} elseif (is_array($data)) {
$model->populateFromArray($data);
}
return $model;
}
}
@@ -0,0 +1,375 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Api;
use OpenStack\Common\HydratorStrategyTrait;
/**
* Represents an individual request parameter in a RESTful operation. A parameter can take on many forms:
* in a URL path, in a URL query, in a JSON body, and in a HTTP header. It is worth documenting brifly each
* variety of parameter:.
*
* * Header parameters are those which populate a HTTP header in a request. Header parameters can have
* aliases; for example, a user-facing name of "Foo" can be sent over the wire as "X-Foo_Bar", as defined
* by ``sentAs``. Prefixes can also be used.
*
* * Query parameters are those which populate a URL query parameter. The value is therefore usually
* confined to a string.
*
* * JSON parameters are those which populate a JSON request body. These are the most complex variety
* of Parameter, since there are so many different ways a JSON document can be constructed. The SDK
* supports deep-nesting according to a XPath syntax; for more information, see {@see \OpenStack\Common\JsonPath}.
* Nested object and array properties are also supported since JSON is a recursive data type. What
* this means is that a Parameter can have an assortment of child Parameters, one for each object
* property or array element.
*
* * Raw parameters are those which populate a non-JSON request body. This is typically used for
* uploading payloads (such as Swift object data) to a remote API.
*
* * Path parameters are those which populate a URL path. They are serialized according to URL
* placeholders.
*/
class Parameter
{
use HydratorStrategyTrait;
const DEFAULT_LOCATION = 'json';
/**
* The human-friendly name of the parameter. This is what the user will input.
*
* @var string
*/
private $name = '';
/**
* The alias for this parameter. Although the user will always interact with the human-friendly $name property,
* the $sentAs is what's used over the wire.
*
* @var string
*/
private $sentAs = '';
/**
* For array parameters (for example, an array of security group names when creating a server), each array element
* will need to adhere to a common schema. For the aforementioned example, each element will need to be a string.
* For more complicated parameters, you might be validated an array of complicated objects.
*
* @var Parameter
*/
private $itemSchema;
/**
* For object parameters, each property will need to adhere to a specific schema. For every property in the
* object, it has its own schema - meaning that this property is a hash of name/schema pairs.
*
* The *only* exception to this rule is for metadata parameters, which are arbitrary key/value pairs. Since it does
* not make sense to have a schema for each metadata key, a common schema is use for every one. So instead of this
* property being a hash of schemas, it is a single Parameter object instead. This single Parameter schema will
* then be applied to each metadata key provided.
*
* @var []Parameter|Parameter
*/
private $properties;
/**
* The value's PHP type which this parameter represents; either "string", "bool", "object", "array", "NULL".
*
* @var string
*/
private $type = '';
/**
* Indicates whether this parameter requires a value from the user.
*
* @var bool
*/
private $required;
/**
* The location in the HTTP request where this parameter will populate; either "header", "url", "query", "raw" or
* "json".
*
* @var string
*/
private $location = '';
/**
* Relevant to "json" location parameters only. This property allows for deep nesting through the use of
* {@see OpenStack\Common\JsonPath}.
*
* @var string
*/
private $path = '';
/**
* Allows for the prefixing of parameter names.
*
* @var string
*/
private $prefix = '';
/**
* The enum values for which this param is restricted.
*
* @var array
*/
private $enum;
public function __construct(array $data)
{
$this->hydrate($data);
$this->required = (bool) $this->required;
$this->stockLocation($data);
$this->stockItemSchema($data);
$this->stockProperties($data);
}
private function stockLocation(array $data)
{
$this->location = isset($data['location']) ? $data['location'] : self::DEFAULT_LOCATION;
if (!AbstractParams::isSupportedLocation($this->location)) {
throw new \RuntimeException(sprintf('%s is not a permitted location', $this->location));
}
}
private function stockItemSchema(array $data)
{
if (isset($data['items'])) {
$this->itemSchema = new Parameter($data['items']);
}
}
private function stockProperties(array $data)
{
if (isset($data['properties'])) {
if ($this->name && false !== stripos($this->name, 'metadata')) {
$this->properties = new Parameter($data['properties']);
} else {
foreach ($data['properties'] as $name => $property) {
$this->properties[$name] = new Parameter($property + ['name' => $name]);
}
}
}
}
/**
* Retrieve the name that will be used over the wire.
*/
public function getName(): string
{
return $this->sentAs ?: $this->name;
}
/**
* Indicates whether the user must provide a value for this parameter.
*/
public function isRequired(): bool
{
return true === $this->required;
}
/**
* Validates a given user value and checks whether it passes basic sanity checking, such as types.
*
* @param $userValues The value provided by the user
*
* @return bool TRUE if the validation passes
*
* @throws \Exception If validation fails
*/
public function validate($userValues): bool
{
$this->validateEnums($userValues);
$this->validateType($userValues);
if ($this->isArray()) {
$this->validateArray($userValues);
} elseif ($this->isObject()) {
$this->validateObject($userValues);
}
return true;
}
private function validateEnums($userValues)
{
if (!empty($this->enum) && 'string' == $this->type && !in_array($userValues, $this->enum)) {
throw new \Exception(sprintf('The only permitted values are %s. You provided %s', implode(', ', $this->enum), print_r($userValues, true)));
}
}
private function validateType($userValues)
{
if (!$this->hasCorrectType($userValues)) {
throw new \Exception(sprintf('The key provided "%s" has the wrong value type. You provided %s (%s) but was expecting %s', $this->name, print_r($userValues, true), gettype($userValues), $this->type));
}
}
private function validateArray($userValues)
{
foreach ($userValues as $userValue) {
$this->itemSchema->validate($userValue);
}
}
private function validateObject($userValues)
{
foreach ($userValues as $key => $userValue) {
$property = $this->getNestedProperty($key);
$property->validate($userValue);
}
}
/**
* Internal method which retrieves a nested property for object parameters.
*
* @param string $key The name of the child parameter
*
* @returns Parameter
*
* @throws \Exception
*/
private function getNestedProperty($key): Parameter
{
if ($this->name && false !== stripos($this->name, 'metadata') && $this->properties instanceof Parameter) {
return $this->properties;
} elseif (isset($this->properties[$key])) {
return $this->properties[$key];
} else {
throw new \Exception(sprintf('The key provided "%s" is not defined', $key));
}
}
/**
* Internal method which indicates whether the user value is of the same type as the one expected
* by this parameter.
*
* @param $userValue The value being checked
*/
private function hasCorrectType($userValue): bool
{
// Helper fn to see whether an array is associative (i.e. a JSON object)
$isAssociative = function ($value) {
return is_array($value) && array_keys($value) !== range(0, count($value) - 1);
};
// For params defined as objects, we'll let the user get away with
// passing in an associative array - since it's effectively a hash
if ('object' == $this->type && $isAssociative($userValue)) {
return true;
}
if (class_exists($this->type) || interface_exists($this->type)) {
return is_a($userValue, $this->type);
}
if (!$this->type) {
return true;
}
// allow string nulls
if ('string' == $this->type && null === $userValue) {
return true;
}
return gettype($userValue) == $this->type;
}
/**
* Indicates whether this parameter represents an array type.
*/
public function isArray(): bool
{
return 'array' == $this->type && $this->itemSchema instanceof Parameter;
}
/**
* Indicates whether this parameter represents an object type.
*/
public function isObject(): bool
{
return 'object' == $this->type && !empty($this->properties);
}
public function getLocation(): string
{
return $this->location;
}
/**
* Verifies whether the given location matches the parameter's location.
*
* @param $value
*/
public function hasLocation($value): bool
{
return $this->location == $value;
}
/**
* Retrieves the parameter's path.
*
* @return string|null
*/
public function getPath(): string
{
return $this->path;
}
/**
* Retrieves the common schema that an array parameter applies to all its child elements.
*
* @return Parameter|null
*/
public function getItemSchema()
{
return $this->itemSchema;
}
/**
* Sets the name of the parameter to a new value.
*/
public function setName(string $name)
{
$this->name = $name;
}
/**
* Retrieves the child parameter for an object parameter.
*
* @param string $name The name of the child property
*
* @return Parameter|null
*/
public function getProperty(string $name)
{
if ($this->properties instanceof Parameter) {
$this->properties->setName($name);
return $this->properties;
}
return isset($this->properties[$name]) ? $this->properties[$name] : null;
}
/**
* Retrieves the prefix for a parameter, if any.
*
* @return string|null
*/
public function getPrefix(): string
{
return $this->prefix;
}
public function getPrefixedName(): string
{
return $this->prefix.$this->getName();
}
}