Обновление клиента (apps, 3rdparty, install)

This commit is contained in:
root
2026-03-16 08:42:57 +00:00
parent b8905de237
commit f390426546
3354 changed files with 505213 additions and 3 deletions
@@ -0,0 +1,349 @@
<?php
declare(strict_types=1);
namespace OpenStack\BlockStorage\v2;
use OpenStack\Common\Api\AbstractApi;
class Api extends AbstractApi
{
public function __construct()
{
$this->params = new Params();
}
public function postVolumes(): array
{
return [
'method' => 'POST',
'path' => 'volumes',
'jsonKey' => 'volume',
'params' => [
'availabilityZone' => $this->params->availabilityZone(),
'sourceVolumeId' => $this->params->sourceVolId(),
'description' => $this->params->desc(),
'snapshotId' => $this->params->snapshotId(),
'size' => $this->params->size(),
'name' => $this->params->name('volume'),
'imageId' => $this->params->imageRef(),
'volumeType' => $this->params->volumeType(),
'metadata' => $this->params->metadata(),
'projectId' => $this->params->projectId(),
],
];
}
public function getVolumes(): array
{
return [
'method' => 'GET',
'path' => 'volumes',
'params' => [
'limit' => $this->params->limit(),
'marker' => $this->params->marker(),
'sort' => $this->params->sort(),
'allTenants' => $this->params->allTenants(),
],
];
}
public function getVolumesDetail(): array
{
return [
'method' => 'GET',
'path' => 'volumes/detail',
'params' => [
'limit' => $this->params->limit(),
'marker' => $this->params->marker(),
'sort' => $this->params->sort(),
'allTenants' => $this->params->allTenants(),
],
];
}
public function getVolume(): array
{
return [
'method' => 'GET',
'path' => 'volumes/{id}',
'params' => [
'id' => $this->params->idPath(),
],
];
}
public function putVolume(): array
{
return [
'method' => 'PUT',
'path' => 'volumes/{id}',
'jsonKey' => 'volume',
'params' => [
'id' => $this->params->idPath(),
'name' => $this->params->name('volume'),
'description' => $this->params->desc(),
],
];
}
public function deleteVolume(): array
{
return [
'method' => 'DELETE',
'path' => 'volumes/{id}',
'params' => ['id' => $this->params->idPath()],
];
}
public function getVolumeMetadata(): array
{
return [
'method' => 'GET',
'path' => 'volumes/{id}/metadata',
'params' => ['id' => $this->params->idPath()],
];
}
public function putVolumeMetadata(): array
{
return [
'method' => 'PUT',
'path' => 'volumes/{id}/metadata',
'params' => [
'id' => $this->params->idPath(),
'metadata' => $this->params->metadata(),
],
];
}
public function getTypes(): array
{
return [
'method' => 'GET',
'path' => 'types',
'params' => [],
];
}
public function postTypes(): array
{
return [
'method' => 'POST',
'path' => 'types',
'jsonKey' => 'volume_type',
'params' => [
'name' => $this->params->name('volume type'),
'specs' => $this->params->typeSpecs(),
],
];
}
public function putType(): array
{
return [
'method' => 'PUT',
'path' => 'types/{id}',
'jsonKey' => 'volume_type',
'params' => [
'id' => $this->params->idPath(),
'name' => $this->params->name('volume type'),
'specs' => $this->params->typeSpecs(),
],
];
}
public function getType(): array
{
return [
'method' => 'GET',
'path' => 'types/{id}',
'params' => ['id' => $this->params->idPath()],
];
}
public function deleteType(): array
{
return [
'method' => 'DELETE',
'path' => 'types/{id}',
'params' => ['id' => $this->params->idPath()],
];
}
public function postSnapshots(): array
{
return [
'method' => 'POST',
'path' => 'snapshots',
'jsonKey' => 'snapshot',
'params' => [
'volumeId' => $this->params->volId(),
'force' => $this->params->force(),
'name' => $this->params->snapshotName(),
'description' => $this->params->desc(),
],
];
}
public function getSnapshots(): array
{
return [
'method' => 'GET',
'path' => 'snapshots',
'params' => [
'marker' => $this->params->marker(),
'limit' => $this->params->limit(),
'sortDir' => $this->params->sortDir(),
'sortKey' => $this->params->sortKey(),
'allTenants' => $this->params->allTenants(),
],
];
}
public function getSnapshotsDetail(): array
{
$api = $this->getSnapshots();
$api['path'] .= '/detail';
return $api;
}
public function getSnapshot(): array
{
return [
'method' => 'GET',
'path' => 'snapshots/{id}',
'params' => ['id' => $this->params->idPath()],
];
}
public function putSnapshot(): array
{
return [
'method' => 'PUT',
'path' => 'snapshots/{id}',
'jsonKey' => 'snapshot',
'params' => [
'id' => $this->params->idPath(),
'name' => $this->params->snapshotName(),
'description' => $this->params->desc(),
],
];
}
public function deleteSnapshot(): array
{
return [
'method' => 'DELETE',
'path' => 'snapshots/{id}',
'params' => ['id' => $this->params->idPath()],
];
}
public function getSnapshotMetadata(): array
{
return [
'method' => 'GET',
'path' => 'snapshots/{id}/metadata',
'params' => ['id' => $this->params->idPath()],
];
}
public function putSnapshotMetadata(): array
{
return [
'method' => 'PUT',
'path' => 'snapshots/{id}/metadata',
'params' => [
'id' => $this->params->idPath(),
'metadata' => $this->params->metadata(),
],
];
}
public function getQuotaSet(): array
{
return [
'method' => 'GET',
'path' => 'os-quota-sets/{tenantId}',
'params' => [
'tenantId' => $this->params->idPath('quota-sets'),
],
];
}
public function deleteQuotaSet(): array
{
return [
'method' => 'DELETE',
'path' => 'os-quota-sets/{tenantId}',
'jsonKey' => 'quota_set',
'params' => [
'tenantId' => $this->params->idPath('quota-sets'),
],
];
}
public function putQuotaSet(): array
{
return [
'method' => 'PUT',
'path' => 'os-quota-sets/{tenantId}',
'jsonKey' => 'quota_set',
'params' => [
'tenantId' => $this->params->idPath(),
'backupGigabytes' => $this->params->quotaSetBackupGigabytes(),
'backups' => $this->params->quotaSetBackups(),
'gigabytes' => $this->params->quotaSetGigabytes(),
'gigabytesIscsi' => $this->params->quotaSetGigabytesIscsi(),
'perVolumeGigabytes' => $this->params->quotaSetPerVolumeGigabytes(),
'snapshots' => $this->params->quotaSetSnapshots(),
'snapshotsIscsi' => $this->params->quotaSetSnapshotsIscsi(),
'volumes' => $this->params->quotaSetVolumes(),
'volumesIscsi' => $this->params->quotaSetVolumesIscsi(),
],
];
}
public function postVolumeBootable(): array
{
return [
'method' => 'POST',
'path' => 'volumes/{id}/action',
'jsonKey' => 'os-set_bootable',
'params' => [
'id' => $this->params->idPath(),
'bootable' => $this->params->bootable(),
],
];
}
public function postImageMetadata(): array
{
return [
'method' => 'POST',
'path' => 'volumes/{id}/action',
'jsonKey' => 'os-set_image_metadata',
'params' => [
'id' => $this->params->idPath(),
'metadata' => $this->params->metadata(),
],
];
}
public function postResetStatus(): array
{
return [
'method' => 'POST',
'path' => 'volumes/{id}/action',
'jsonKey' => 'os-reset_status',
'params' => [
'id' => $this->params->idPath(),
'status' => $this->params->volumeStatus(),
'migrationStatus' => $this->params->volumeMigrationStatus(),
'attachStatus' => $this->params->volumeAttachStatus(),
],
];
}
}
@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace OpenStack\BlockStorage\v2\Models;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
/**
* Represents a BlockStorage v2 Quota Set.
*
* @property \OpenStack\BlockStorage\v2\Api $api
*/
class QuotaSet extends OperatorResource implements Retrievable, Updateable, Deletable
{
/** @var string */
public $tenantId;
/** @var int */
public $backupGigabytes;
/** @var int */
public $backups;
/** @var int */
public $gigabytes;
/** @var int */
public $gigabytesIscsi;
/** @var int */
public $perVolumeGigabytes;
/** @var int */
public $snapshots;
/** @var int */
public $snapshotsIscsi;
/** @var int */
public $volumes;
/** @var int */
public $volumesIscsi;
protected $aliases = [
'backup_gigabytes' => 'backupGigabytes',
'gigabytes' => 'gigabytes',
'gigabytes_iscsi' => 'gigabytesIscsi',
'per_volume_gigabytes' => 'perVolumeGigabytes',
'snapshots_iscsi' => 'snapshotsIscsi',
'volumes_iscsi' => 'volumesIscsi',
'id' => 'tenantId',
];
protected $resourceKey = 'quota_set';
public function retrieve()
{
$response = $this->execute($this->api->getQuotaSet(), ['tenantId' => (string) $this->tenantId]);
$this->populateFromResponse($response);
}
public function update()
{
$response = $this->executeWithState($this->api->putQuotaSet());
$this->populateFromResponse($response);
}
public function delete()
{
$response = $this->executeWithState($this->api->deleteQuotaSet());
$this->populateFromResponse($response);
}
}
@@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
namespace OpenStack\BlockStorage\v2\Models;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\HasMetadata;
use OpenStack\Common\Resource\HasWaiterTrait;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
use OpenStack\Common\Transport\Utils;
use Psr\Http\Message\ResponseInterface;
/**
* @property \OpenStack\BlockStorage\v2\Api $api
*/
class Snapshot extends OperatorResource implements Listable, Creatable, Updateable, Deletable, Retrievable, HasMetadata
{
use HasWaiterTrait;
/** @var string */
public $id;
/** @var string */
public $name;
/** @var string */
public $status;
/** @var string */
public $description;
/** @var \DateTimeImmutable */
public $createdAt;
/** @var array */
public $metadata = [];
/** @var string */
public $volumeId;
/** @var int */
public $size;
/** @var string */
public $projectId;
protected $resourceKey = 'snapshot';
protected $resourcesKey = 'snapshots';
protected $markerKey = 'id';
protected $aliases = [
'volume_id' => 'volumeId',
'os-extended-snapshot-attributes:project_id' => 'projectId',
];
protected function getAliases(): array
{
return parent::getAliases() + [
'created_at' => new Alias('createdAt', \DateTimeImmutable::class),
];
}
public function populateFromResponse(ResponseInterface $response): self
{
parent::populateFromResponse($response);
$this->metadata = $this->parseMetadata($response);
return $this;
}
public function retrieve()
{
$response = $this->executeWithState($this->api->getSnapshot());
$this->populateFromResponse($response);
}
/**
* @param array $userOptions {@see \OpenStack\BlockStorage\v2\Api::postSnapshots}
*/
public function create(array $userOptions): Creatable
{
$response = $this->execute($this->api->postSnapshots(), $userOptions);
return $this->populateFromResponse($response);
}
public function update()
{
$this->executeWithState($this->api->putSnapshot());
}
public function delete()
{
$this->executeWithState($this->api->deleteSnapshot());
}
public function getMetadata(): array
{
$response = $this->executeWithState($this->api->getSnapshotMetadata());
$this->metadata = $this->parseMetadata($response);
return $this->metadata;
}
public function mergeMetadata(array $metadata)
{
$this->getMetadata();
$this->metadata = array_merge($this->metadata, $metadata);
$this->executeWithState($this->api->putSnapshotMetadata());
}
public function resetMetadata(array $metadata)
{
$this->metadata = $metadata;
$this->executeWithState($this->api->putSnapshotMetadata());
}
public function parseMetadata(ResponseInterface $response): array
{
$json = Utils::jsonDecode($response);
return isset($json['metadata']) ? $json['metadata'] : [];
}
}
@@ -0,0 +1,184 @@
<?php
declare(strict_types=1);
namespace OpenStack\BlockStorage\v2\Models;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\HasMetadata;
use OpenStack\Common\Resource\HasWaiterTrait;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
use OpenStack\Common\Transport\Utils;
use Psr\Http\Message\ResponseInterface;
/**
* @property \OpenStack\BlockStorage\v2\Api $api
*/
class Volume extends OperatorResource implements Creatable, Listable, Updateable, Deletable, Retrievable, HasMetadata
{
use HasWaiterTrait;
/** @var string */
public $id;
/** @var int */
public $size;
/** @var string */
public $status;
/** @var string */
public $name;
/** @var array */
public $attachments;
/** @var string */
public $availabilityZone;
/** @var \DateTimeImmutable */
public $createdAt;
/** @var string */
public $description;
/** @var string */
public $volumeTypeName;
/** @var string */
public $snapshotId;
/** @var string */
public $sourceVolumeId;
/** @var string */
public $tenantId;
/** @var string */
public $host;
/** @var string */
public $bootable;
/** @var array */
public $metadata = [];
/** @var array */
public $volumeImageMetadata = [];
protected $resourceKey = 'volume';
protected $resourcesKey = 'volumes';
protected $markerKey = 'id';
protected $aliases = [
'availability_zone' => 'availabilityZone',
'source_volid' => 'sourceVolumeId',
'snapshot_id' => 'snapshotId',
'volume_type' => 'volumeTypeName',
'os-vol-tenant-attr:tenant_id' => 'tenantId',
'os-vol-host-attr:host' => 'host',
'volume_image_metadata' => 'volumeImageMetadata',
];
protected function getAliases(): array
{
return parent::getAliases() + [
'created_at' => new Alias('createdAt', \DateTimeImmutable::class),
];
}
public function populateFromResponse(ResponseInterface $response): self
{
parent::populateFromResponse($response);
$this->metadata = $this->parseMetadata($response);
return $this;
}
public function retrieve()
{
$response = $this->executeWithState($this->api->getVolume());
$this->populateFromResponse($response);
}
/**
* @param array $userOptions {@see \OpenStack\BlockStorage\v2\Api::postVolumes}
*/
public function create(array $userOptions): Creatable
{
$response = $this->execute($this->api->postVolumes(), $userOptions);
return $this->populateFromResponse($response);
}
public function update()
{
$response = $this->executeWithState($this->api->putVolume());
$this->populateFromResponse($response);
}
public function delete()
{
$this->executeWithState($this->api->deleteVolume());
}
public function getMetadata(): array
{
$response = $this->executeWithState($this->api->getVolumeMetadata());
$this->metadata = $this->parseMetadata($response);
return $this->metadata;
}
public function mergeMetadata(array $metadata)
{
$this->getMetadata();
$this->metadata = array_merge($this->metadata, $metadata);
$this->executeWithState($this->api->putVolumeMetadata());
}
public function resetMetadata(array $metadata)
{
$this->metadata = $metadata;
$this->executeWithState($this->api->putVolumeMetadata());
}
public function parseMetadata(ResponseInterface $response): array
{
$json = Utils::jsonDecode($response);
return isset($json['metadata']) ? $json['metadata'] : [];
}
/**
* Update the bootable status for a volume, mark it as a bootable volume.
*/
public function setBootable(bool $bootable = true)
{
$this->execute($this->api->postVolumeBootable(), ['id' => $this->id, 'bootable' => $bootable]);
}
/**
* Sets the image metadata for a volume.
*/
public function setImageMetadata(array $metadata)
{
$this->execute($this->api->postImageMetadata(), ['id' => $this->id, 'metadata' => $metadata]);
}
/**
* Administrator only. Resets the status, attach status, and migration status for a volume. Specify the os-reset_status action in the request body.
*
* @see https://developer.openstack.org/api-ref/block-storage/v2/index.html#volume-actions-volumes-action
*/
public function resetStatus(array $options)
{
$options = array_merge($options, ['id' => $this->id]);
$this->execute($this->api->postResetStatus(), $options);
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace OpenStack\BlockStorage\v2\Models;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
/**
* @property \OpenStack\BlockStorage\v2\Api $api
*/
class VolumeAttachment extends OperatorResource implements Listable
{
/** @var string */
public $id;
/** @var int */
public $device;
/** @var string */
public $serverId;
/** @var string */
public $volumeId;
protected $resourceKey = 'volumeAttachment';
protected $resourcesKey = 'volumeAttachments';
}
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace OpenStack\BlockStorage\v2\Models;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
/**
* @property \OpenStack\BlockStorage\v2\Api $api
*/
class VolumeType extends OperatorResource implements Listable, Creatable, Updateable, Deletable, Retrievable
{
/** @var string */
public $id;
/** @var string */
public $name;
protected $resourceKey = 'volume_type';
protected $resourcesKey = 'volume_types';
/**
* @param array $userOptions {@see \OpenStack\BlockStorage\v2\Api::postTypes}
*/
public function create(array $userOptions): Creatable
{
$response = $this->execute($this->api->postTypes(), $userOptions);
return $this->populateFromResponse($response);
}
public function retrieve()
{
$response = $this->executeWithState($this->api->getType());
$this->populateFromResponse($response);
}
public function update()
{
$this->executeWithState($this->api->putType());
}
public function delete()
{
$this->executeWithState($this->api->deleteType());
}
}
@@ -0,0 +1,280 @@
<?php
declare(strict_types=1);
namespace OpenStack\BlockStorage\v2;
use OpenStack\Common\Api\AbstractParams;
class Params extends AbstractParams
{
public function availabilityZone(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'sentAs' => 'availability_zone',
'description' => 'The availability zone where the entity will reside.',
];
}
public function sourceVolId(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'sentAs' => 'source_volid',
'description' => 'To create a volume from an existing volume, specify the ID of the existing volume. The '.
'volume is created with the same size as the source volume.',
];
}
public function desc(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'description' => 'A human-friendly description that describes the resource',
];
}
public function snapshotId(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'sentAs' => 'snapshot_id',
'description' => 'To create a volume from an existing snapshot, specify the ID of the existing volume '.
'snapshot. The volume is created in same availability zone and with same size as the snapshot.',
];
}
public function size(): array
{
return [
'type' => self::INT_TYPE,
'location' => self::JSON,
'required' => true,
'description' => 'The size of the volume, in gibibytes (GiB).',
];
}
public function imageRef(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'sentAs' => 'imageRef',
'description' => 'The ID of the image from which you want to create the volume. Required to create a '.
'bootable volume.',
];
}
public function volumeType(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'sentAs' => 'volume_type',
'description' => 'The associated volume type.',
];
}
public function bootable(): array
{
return [
'type' => self::BOOL_TYPE,
'location' => self::JSON,
'description' => 'Enables or disables the bootable attribute. You can boot an instance from a bootable volume.',
];
}
public function volumeStatus(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'required' => true,
'description' => 'The volume status.',
];
}
public function volumeMigrationStatus(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'required' => false,
'description' => 'The volume migration status.',
'sentAs' => 'migration_status',
];
}
public function volumeAttachStatus(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'required' => false,
'description' => 'The volume attach status.',
'sentAs' => 'attach_status',
];
}
public function metadata(): array
{
return [
'type' => self::OBJECT_TYPE,
'location' => self::JSON,
'description' => 'One or more metadata key and value pairs to associate with the volume.',
'properties' => [
'type' => self::STRING_TYPE,
'description' => <<<TYPEOTHER
The value being set for your key. Bear in mind that "key" is just an example, you can name it anything.
TYPEOTHER
],
];
}
public function sort(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::QUERY,
'description' => 'Comma-separated list of sort keys and optional sort directions in the form of '.
'<key>[:<direction>]. A valid direction is asc (ascending) or desc (descending).',
];
}
public function name(string $resource): array
{
return parent::name($resource) + [
'type' => self::STRING_TYPE,
'location' => self::JSON,
];
}
public function idPath(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::URL,
'description' => 'The UUID of the resource',
'documented' => false,
];
}
public function typeSpecs(): array
{
return [
'type' => self::OBJECT_TYPE,
'location' => self::JSON,
'description' => 'A key and value pair that contains additional specifications that are associated with '.
'the volume type. Examples include capabilities, capacity, compression, and so on, depending on the '.
'storage driver in use.',
];
}
public function volId(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'required' => true,
'sentAs' => 'volume_id',
'description' => 'To create a snapshot from an existing volume, specify the ID of the existing volume.',
];
}
public function force(): array
{
return [
'type' => self::BOOL_TYPE,
'location' => self::JSON,
'description' => 'Indicate whether to snapshot, even if the volume is attached. Default is false.',
];
}
public function snapshotName(): array
{
return parent::name('snapshot') + [
'type' => self::STRING_TYPE,
'location' => self::JSON,
];
}
protected function quotaSetLimit($sentAs, $description): array
{
return [
'type' => self::INT_TYPE,
'location' => self::JSON,
'sentAs' => $sentAs,
'description' => $description,
];
}
public function quotaSetLimitInstances(): array
{
return $this->quotaSetLimit('instances', 'The number of allowed instances for each tenant.');
}
public function quotaSetBackupGigabytes(): array
{
return $this->quotaSetLimit('backup_gigabytes', 'Total size of back-up storage (GiB)');
}
public function quotaSetBackups(): array
{
return $this->quotaSetLimit('backups', 'The number of allowed back-ups');
}
public function quotaSetGigabytes(): array
{
return $this->quotaSetLimit('gigabytes', 'Total Size of Volumes and Snapshots (GiB)');
}
public function quotaSetGigabytesIscsi(): array
{
return $this->quotaSetLimit('gigabytes_iscsi', 'Total Size of Volumes and Snapshots iscsi (GiB)');
}
public function quotaSetTenantId(): array
{
return $this->quotaSetLimit('id', 'Tenant Id');
}
public function quotaSetPerVolumeGigabytes(): array
{
return $this->quotaSetLimit('per_volume_gigabytes', 'Allowed size per Volume (GiB)');
}
public function quotaSetSnapshots(): array
{
return $this->quotaSetLimit('snapshots', 'The number of allowed snapshots');
}
public function quotaSetSnapshotsIscsi(): array
{
return $this->quotaSetLimit('snapshots_iscsi', 'The number of allowed snapshots iscsi');
}
public function quotaSetVolumes(): array
{
return $this->quotaSetLimit('volumes', 'The number of allowed volumes');
}
public function quotaSetVolumesIscsi(): array
{
return $this->quotaSetLimit('volumes_iscsi', 'The number of allowed volumes iscsi');
}
public function projectId(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::URL,
'sentAs' => 'project_id',
'description' => 'The UUID of the project in a multi-tenancy cloud.',
];
}
}
@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace OpenStack\BlockStorage\v2;
use OpenStack\BlockStorage\v2\Models\QuotaSet;
use OpenStack\BlockStorage\v2\Models\Snapshot;
use OpenStack\BlockStorage\v2\Models\Volume;
use OpenStack\BlockStorage\v2\Models\VolumeType;
use OpenStack\Common\Service\AbstractService;
/**
* @property Api $api
*/
class Service extends AbstractService
{
/**
* Provisions a new bootable volume, based either on an existing volume, image or snapshot.
* You must have enough volume storage quota remaining to create a volume of size requested.
*
* @param array $userOptions {@see Api::postVolumes}
*/
public function createVolume(array $userOptions): Volume
{
return $this->model(Volume::class)->create($userOptions);
}
/**
* Lists all available volumes.
*
* @param bool $detail if set to TRUE, more information will be returned
* @param array $userOptions {@see Api::getVolumes}
*
* @return \Generator<mixed, \OpenStack\BlockStorage\v2\Models\Volume>
*/
public function listVolumes(bool $detail = false, array $userOptions = []): \Generator
{
$def = (true === $detail) ? $this->api->getVolumesDetail() : $this->api->getVolumes();
return $this->model(Volume::class)->enumerate($def, $userOptions);
}
/**
* @param string $volumeId the UUID of the volume being retrieved
*/
public function getVolume(string $volumeId): Volume
{
$volume = $this->model(Volume::class);
$volume->populateFromArray(['id' => $volumeId]);
return $volume;
}
/**
* @param array $userOptions {@see Api::postTypes}
*/
public function createVolumeType(array $userOptions): VolumeType
{
return $this->model(VolumeType::class)->create($userOptions);
}
/**
* @return \Generator<mixed, \OpenStack\BlockStorage\v2\Models\VolumeType>
*/
public function listVolumeTypes(): \Generator
{
return $this->model(VolumeType::class)->enumerate($this->api->getTypes(), []);
}
public function getVolumeType(string $typeId): VolumeType
{
$type = $this->model(VolumeType::class);
$type->populateFromArray(['id' => $typeId]);
return $type;
}
/**
* @param array $userOptions {@see Api::postSnapshots}
*/
public function createSnapshot(array $userOptions): Snapshot
{
return $this->model(Snapshot::class)->create($userOptions);
}
/**
* @return \Generator<mixed, \OpenStack\BlockStorage\v2\Models\Snapshot>
*/
public function listSnapshots(bool $detail = false, array $userOptions = []): \Generator
{
$def = (true === $detail) ? $this->api->getSnapshotsDetail() : $this->api->getSnapshots();
return $this->model(Snapshot::class)->enumerate($def, $userOptions);
}
public function getSnapshot(string $snapshotId): Snapshot
{
$snapshot = $this->model(Snapshot::class);
$snapshot->populateFromArray(['id' => $snapshotId]);
return $snapshot;
}
/**
* Shows A Quota for a tenant.
*/
public function getQuotaSet(string $tenantId): QuotaSet
{
$quotaSet = $this->model(QuotaSet::class);
$quotaSet->populateFromResponse($this->execute($this->api->getQuotaSet(), ['tenantId' => $tenantId]));
return $quotaSet;
}
}
@@ -0,0 +1,7 @@
<?php
namespace OpenStack\BlockStorage\v3;
class Api extends \OpenStack\BlockStorage\v2\Api
{
}
@@ -0,0 +1,7 @@
<?php
namespace OpenStack\BlockStorage\v3;
class Service extends \OpenStack\BlockStorage\v2\Service
{
}
@@ -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,113 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Api;
abstract class AbstractParams
{
// locations
public const QUERY = 'query';
public const HEADER = 'header';
public const URL = 'url';
public const JSON = 'json';
public const RAW = 'raw';
// types
public const STRING_TYPE = 'string';
public const BOOL_TYPE = 'boolean';
public const BOOLEAN_TYPE = self::BOOL_TYPE;
public const OBJECT_TYPE = 'object';
public const ARRAY_TYPE = 'array';
public const NULL_TYPE = 'NULL';
public const INT_TYPE = 'integer';
public 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,
'sentAs' => 'sort_dir',
'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,
'sentAs' => 'sort_key',
'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 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,132 @@
<?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;
/** @var bool Whether this operation should skip authentication */
private $skipAuth;
/**
* @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']);
$this->skipAuth = $definition['skipAuth'] ?? false;
}
public function getPath(): string
{
return $this->path;
}
public function getMethod(): string
{
return $this->method;
}
/**
* Indicates if operation must be run without authentication. This is useful for getting authentication tokens.
*/
public function getSkipAuth(): bool
{
return $this->skipAuth;
}
/**
* Indicates whether this operation supports a parameter.
*
* @param string $key The name of a parameter
*/
public function hasParam(string $key): bool
{
return isset($this->params[$key]);
}
/**
* @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,144 @@
<?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;
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 string $methodName the name of the method being invoked
* @param array $args the arguments to be passed to the sequential method
*
* @return Promise
*
* @throws \RuntimeException If method does not exist
*/
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);
}
public function getOperation(array $definition): Operation
{
return new Operation($definition);
}
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 (isset($userValues['requestOptions'])) {
$options += $userValues['requestOptions'];
// headers are always created in options, merge them
if (isset($userValues['requestOptions']['headers'])) {
$options['headers'] = array_merge($options['headers'], $userValues['requestOptions']['headers']);
}
}
$options['openstack.skip_auth'] = $operation->getSkipAuth();
return $this->client->$method($operation->getMethod(), $uri, $options);
}
public function execute(array $definition, array $userValues = []): ResponseInterface
{
return $this->sendRequest($this->getOperation($definition), $userValues);
}
public function executeAsync(array $definition, array $userValues = []): PromiseInterface
{
return $this->sendRequest($this->getOperation($definition), $userValues, true);
}
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,373 @@
<?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;
public 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.
*/
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();
}
}
@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common;
/**
* Encapsulates common logic for classes which implement the SPL \ArrayAccess interface.
*/
trait ArrayAccessTrait
{
/**
* The internal state that this object represents.
*
* @var array
*/
private $internalState = [];
/**
* Sets an internal key with a value.
*
* @param string $offset
*/
public function offsetSet($offset, $value)
{
if (null === $offset) {
$this->internalState[] = $value;
} else {
$this->internalState[$offset] = $value;
}
}
/**
* Checks whether an internal key exists.
*/
public function offsetExists(string $offset): bool
{
return isset($this->internalState[$offset]);
}
/**
* Unsets an internal key.
*/
public function offsetUnset(string $offset)
{
unset($this->internalState[$offset]);
}
/**
* Retrieves an internal key.
*
* @return mixed|null
*/
public function offsetGet(string $offset)
{
return $this->offsetExists($offset) ? $this->internalState[$offset] : null;
}
}
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Auth;
use GuzzleHttp\Psr7\Utils;
use Psr\Http\Message\RequestInterface;
/**
* This class is responsible for three tasks:.
*
* 1. performing the initial authentication for OpenStack services
* 2. populating the ``X-Auth-Token`` header for every HTTP request
* 3. checking the token expiry before each request, and re-authenticating if necessary
*/
class AuthHandler
{
/** @var callable */
private $nextHandler;
/** @var callable */
private $tokenGenerator;
/** @var Token */
private $token;
public function __construct(callable $nextHandler, callable $tokenGenerator, ?Token $token = null)
{
$this->nextHandler = $nextHandler;
$this->tokenGenerator = $tokenGenerator;
$this->token = $token;
}
/**
* This method is invoked before every HTTP request is sent to the API. When this happens, it
* checks to see whether a token is set and valid, and then sets the ``X-Auth-Token`` header
* for the HTTP request before letting it continue on its merry way.
*
* @return mixed|void
*/
public function __invoke(RequestInterface $request, array $options)
{
$fn = $this->nextHandler;
if (!isset($options['openstack.skip_auth'])) {
// Deprecated. Left for backward compatibility only.
if ($this->shouldIgnore($request)) {
return $fn($request, $options);
}
} elseif ($options['openstack.skip_auth']) {
return $fn($request, $options);
}
if (!$this->token || $this->token->hasExpired()) {
$this->token = call_user_func($this->tokenGenerator);
}
$modify = ['set_headers' => ['X-Auth-Token' => $this->token->getId()]];
return $fn(Utils::modifyRequest($request, $modify), $options);
}
/**
* Internal method which prevents infinite recursion. For certain requests, like the initial
* auth call itself, we do NOT want to send a token.
*/
private function shouldIgnore(RequestInterface $request): bool
{
return false !== strpos((string) $request->getUri(), 'tokens') && 'POST' == $request->getMethod();
}
}
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Auth;
interface Catalog
{
/**
* Attempts to retrieve the base URL for a service from the catalog according to the arguments provided.
*
* @param string $name The name of the service as it appears in the catalog
* @param string $type The type of the service as it appears in the catalog
* @param string $region The region of the service as it appears in the catalog
* @param string $urlType The URL type of the service as it appears in the catalog
*
* @throws \RuntimeException If no endpoint is matched
*
* @returns string
*/
public function getServiceUrl(string $name, string $type, string $region, string $urlType): string;
}
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Auth;
interface IdentityService
{
/**
* Authenticates and retrieves back a token and catalog.
*
* @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;
}
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Auth;
interface Token
{
public function getId(): string;
/**
* Indicates whether the token has expired or not.
*
* @return bool TRUE if the token has expired, FALSE if it is still valid
*/
public function hasExpired(): bool;
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Error;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Represents a HTTP-specific error, caused by 4xx or 5xx response statuses.
*/
class BadResponseError extends BaseError
{
/** @var RequestInterface */
private $request;
/** @var ResponseInterface */
private $response;
public function setRequest(RequestInterface $request)
{
$this->request = $request;
}
public function setResponse(ResponseInterface $response)
{
$this->response = $response;
}
public function getRequest(): RequestInterface
{
return $this->request;
}
public function getResponse(): ResponseInterface
{
return $this->response;
}
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Error;
/**
* Base error class.
*/
class BaseError extends \Exception
{
}
@@ -0,0 +1,187 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Error;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\ClientException;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Class responsible for building meaningful exceptions. For HTTP problems, it produces a {@see HttpError}
* exception, and supplies a error message with reasonable defaults. For user input problems, it produces a
* {@see UserInputError} exception. For both, the problem is described, a potential solution is offered and
* a link to further information is included.
*/
class Builder
{
public const MAX_BODY_LENGTH = 5000;
/**
* The default domain to use for further link documentation.
*
* @var string
*/
private $docDomain = 'http://docs.php-opencloud.com/en/latest/';
/**
* The HTTP client required to validate the further links.
*
* @var ClientInterface
*/
private $client;
public function __construct(?ClientInterface $client = null)
{
$this->client = $client ?: new Client();
}
/**
* Internal method used when outputting headers in the error description.
*/
private function header(string $name): string
{
return sprintf("%s\n%s\n", $name, str_repeat('~', strlen($name)));
}
/**
* Before outputting custom links, it is validated to ensure that the user is not
* directed off to a broken link. If a 404 is detected, it is hidden.
*
* @param $link The proposed link
*/
private function linkIsValid(string $link): bool
{
$link = $this->docDomain.$link;
try {
return $this->client->request('HEAD', $link)->getStatusCode() < 400;
} catch (ClientException $e) {
return false;
}
}
/**
* @codeCoverageIgnore
*/
public function str(MessageInterface $message, int $verbosity = 0): string
{
if ($message instanceof RequestInterface) {
$msg = trim($message->getMethod().' '.$message->getRequestTarget());
$msg .= ' HTTP/'.$message->getProtocolVersion();
if (!$message->hasHeader('host')) {
$msg .= "\r\nHost: ".$message->getUri()->getHost();
}
} 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);
}
if ($verbosity < 2) {
return $msg;
}
$contentType = strtolower($message->getHeaderLine('content-type'));
if (false !== strpos($contentType, 'application/json')) {
$body = $message->getBody()->read(self::MAX_BODY_LENGTH);
$msg .= "\r\n\r\n".$body;
if ('' !== $message->getBody()->read(1)) {
$msg .= '...';
}
}
return trim($msg);
}
/**
* Helper method responsible for constructing and returning {@see BadResponseError} exceptions.
*
* @param RequestInterface $request The faulty request
* @param ResponseInterface $response The error-filled response
*/
public function httpError(RequestInterface $request, ResponseInterface $response, int $verbosity = 0): BadResponseError
{
$message = $this->header('HTTP Error');
$message .= sprintf(
"The remote server returned a \"%d %s\" error for the following transaction:\n\n",
$response->getStatusCode(),
$response->getReasonPhrase()
);
$message .= $this->header('Request');
$message .= $this->str($request, $verbosity).PHP_EOL.PHP_EOL;
$message .= $this->header('Response');
$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.';
$e = new BadResponseError($message);
$e->setRequest($request);
$e->setResponse($response);
return $e;
}
private function getStatusCodeMessage(int $statusCode): string
{
$errors = [
400 => 'Please ensure that your input values are valid and well-formed. ',
401 => 'Please ensure that your authentication credentials are valid. ',
404 => "Please ensure that the resource you're trying to access actually exists. ",
500 => 'Please try this operation again once you know the remote server is operational. ',
];
return isset($errors[$statusCode]) ? $errors[$statusCode] : '';
}
/**
* 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)
*/
public function userInputError(string $expectedType, $userValue, ?string $furtherLink = null): UserInputError
{
$message = $this->header('User Input Error');
$message .= sprintf(
"%s was expected, but the following value was passed in:\n\n%s\n",
$expectedType,
print_r($userValue, true)
);
$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 .= 'If you run into trouble, please open a support issue on https://github.com/php-opencloud/openstack/issues.';
return new UserInputError($message);
}
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Error;
/**
* Error to indicate functionality which has not been implemented yet.
*/
class NotImplementedError extends BaseError
{
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Error;
/**
* Represents a user input error, caused by an incorrect type or malformed value.
*/
class UserInputError extends BaseError
{
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common;
/**
* Represents common functionality for populating, or "hydrating", an object with arbitrary data.
*/
trait HydratorStrategyTrait
{
/**
* Hydrates an object with set data.
*
* @param array $data The data to set
* @param array $aliases Any aliases
*/
public function hydrate(array $data, array $aliases = [])
{
foreach ($data as $key => $val) {
$key = isset($aliases[$key]) ? $aliases[$key] : $key;
if (property_exists($this, $key)) {
$this->{$key} = $val;
}
}
}
public function set(string $key, $property, array $data, ?callable $fn = null)
{
if (isset($data[$key]) && property_exists($this, $property)) {
$value = $fn ? call_user_func($fn, $data[$key]) : $data[$key];
$this->$property = $value;
}
}
}
+109
View File
@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common;
/**
* This class allows arbitrary data structures to be inserted into, and extracted from, deep arrays
* and JSON-serialized strings. Say, for example, that you have this array as an input:.
*
* <pre><code>['foo' => ['bar' => ['baz' => 'some_value']]]</code></pre>
*
* and you wanted to insert or extract an element. Usually, you would use:
*
* <pre><code>$array['foo']['bar']['baz'] = 'new_value';</code></pre>
*
* but sometimes you do not have access to the variable - so a string representation is needed. Using
* XPath-like syntax, this class allows you to do this:
*
* <pre><code>$jsonPath = new JsonPath($array);
* $jsonPath->set('foo.bar.baz', 'new_value');
* $val = $jsonPath->get('foo.bar.baz');
* </code></pre>
*/
class JsonPath
{
/** @var array */
private $jsonStructure;
/**
* @param $structure The initial data structure to extract from and insert into. Typically this will be a
* multidimensional associative array; but well-formed JSON strings are also acceptable.
*/
public function __construct($structure)
{
$this->jsonStructure = is_string($structure) ? json_decode($structure, true) : $structure;
}
/**
* Set a node in the structure.
*
* @param $path The XPath to use
* @param $value The new value of the node
*/
public function set(string $path, $value)
{
$this->jsonStructure = $this->setPath($path, $value, $this->jsonStructure);
}
/**
* Internal method for recursive calls.
*
* @return mixed
*/
private function setPath(string $path, $value, array $json): array
{
$nodes = explode('.', $path);
$point = array_shift($nodes);
if (!isset($json[$point])) {
$json[$point] = [];
}
if (!empty($nodes)) {
$json[$point] = $this->setPath(implode('.', $nodes), $value, $json[$point]);
} else {
$json[$point] = $value;
}
return $json;
}
/**
* Return the updated structure.
*/
public function getStructure()
{
return $this->jsonStructure;
}
/**
* Get a path's value. If no path can be matched, NULL is returned.
*
* @return mixed|null
*/
public function get(string $path)
{
return $this->getPath($path, $this->jsonStructure);
}
/**
* Internal method for recursion.
*/
private function getPath(string $path, $json)
{
$nodes = explode('.', $path);
$point = array_shift($nodes);
if (!isset($json[$point])) {
return null;
}
if (empty($nodes)) {
return $json[$point];
} else {
return $this->getPath(implode('.', $nodes), $json[$point]);
}
}
}
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\JsonSchema;
class JsonPatch
{
public const OP_ADD = 'add';
public const OP_REPLACE = 'replace';
public const OP_REMOVE = 'remove';
public static function diff($src, $dest)
{
return (new static())->makeDiff($src, $dest);
}
public function makeDiff($srcStruct, $desStruct, string $path = ''): array
{
$changes = [];
if (is_object($srcStruct)) {
$changes = $this->handleObject($srcStruct, $desStruct, $path);
} elseif (is_array($srcStruct)) {
$changes = $this->handleArray($srcStruct, $desStruct, $path);
} elseif ($srcStruct != $desStruct) {
$changes[] = $this->makePatch(self::OP_REPLACE, $path, $desStruct);
}
return $changes;
}
protected function handleArray(array $srcStruct, array $desStruct, string $path): array
{
$changes = [];
if ($diff = $this->arrayDiff($desStruct, $srcStruct)) {
foreach ($diff as $key => $val) {
if (is_object($val)) {
$changes = array_merge($changes, $this->makeDiff($srcStruct[$key], $val, $this->path($path, $key)));
} else {
$op = array_key_exists($key, $srcStruct) && !in_array($srcStruct[$key], $desStruct, true)
? self::OP_REPLACE : self::OP_ADD;
$changes[] = $this->makePatch($op, $this->path($path, $key), $val);
}
}
} elseif ($srcStruct != $desStruct) {
foreach ($srcStruct as $key => $val) {
if (!in_array($val, $desStruct, true)) {
$changes[] = $this->makePatch(self::OP_REMOVE, $this->path($path, $key));
}
}
}
return $changes;
}
protected function handleObject(\stdClass $srcStruct, \stdClass $desStruct, string $path): array
{
$changes = [];
if ($this->shouldPartiallyReplace($srcStruct, $desStruct)) {
foreach ($desStruct as $key => $val) {
if (!property_exists($srcStruct, $key)) {
$changes[] = $this->makePatch(self::OP_ADD, $this->path($path, $key), $val);
} elseif ($srcStruct->$key != $val) {
$changes = array_merge($changes, $this->makeDiff($srcStruct->$key, $val, $this->path($path, $key)));
}
}
} elseif ($this->shouldPartiallyReplace($desStruct, $srcStruct)) {
foreach ($srcStruct as $key => $val) {
if (!property_exists($desStruct, $key)) {
$changes[] = $this->makePatch(self::OP_REMOVE, $this->path($path, $key));
}
}
}
return $changes;
}
protected function shouldPartiallyReplace(\stdClass $o1, \stdClass $o2): bool
{
// NOTE: count(stdClass) always returns 1
return count(array_diff_key((array) $o1, (array) $o2)) < 1;
}
protected function arrayDiff(array $a1, array $a2): array
{
$result = [];
foreach ($a1 as $key => $val) {
if (!in_array($val, $a2, true)) {
$result[$key] = $val;
}
}
return $result;
}
protected function path(string $root, $path): string
{
$path = (string) $path;
if ('_empty_' === $path) {
$path = '';
}
return rtrim($root, '/').'/'.ltrim($path, '/');
}
protected function makePatch(string $op, string $path, $val = null): array
{
switch ($op) {
default:
return ['op' => $op, 'path' => $path, 'value' => $val];
case self::OP_REMOVE:
return ['op' => $op, 'path' => $path];
}
}
}
@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\JsonSchema;
use JsonSchema\Validator;
class Schema
{
/** @var object */
private $body;
/** @var Validator */
private $validator;
public function __construct($body, ?Validator $validator = null)
{
$this->body = (object) $body;
$this->validator = $validator ?: new Validator();
}
public function getPropertyPaths(): array
{
$paths = [];
foreach ($this->body->properties as $propertyName => $property) {
$paths[] = sprintf('/%s', $propertyName);
}
return $paths;
}
public function normalizeObject($subject, array $aliases): \stdClass
{
$out = new \stdClass();
foreach ($this->body->properties as $propertyName => $property) {
$name = $aliases[$propertyName] ?? $propertyName;
if (isset($property->readOnly) && true === $property->readOnly) {
continue;
} elseif (property_exists($subject, $name)) {
$out->$propertyName = $subject->$name;
} elseif (property_exists($subject, $propertyName)) {
$out->$propertyName = $subject->$propertyName;
}
}
return $out;
}
public function validate($data)
{
$this->validator->check($data, $this->body);
}
public function isValid(): bool
{
return $this->validator->isValid();
}
public function getErrors(): array
{
return $this->validator->getErrors();
}
public function getErrorString(): string
{
$msg = "Provided values do not validate. Errors:\n";
foreach ($this->getErrors() as $error) {
$msg .= sprintf("[%s] %s\n", $error['property'], $error['message']);
}
return $msg;
}
}
@@ -0,0 +1,162 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Resource;
use OpenStack\Common\Transport\Serializable;
use OpenStack\Common\Transport\Utils;
use Psr\Http\Message\ResponseInterface;
/**
* Represents a top-level abstraction of a remote API resource. Usually a resource represents a discrete
* entity such as a Server, Container, Load Balancer. Apart from a representation of state, a resource can
* also execute RESTFul operations on itself (updating, deleting, listing) or on other models.
*/
abstract class AbstractResource implements ResourceInterface, Serializable
{
/**
* The JSON key that indicates how the API nests singular resources. For example, when
* performing a GET, it could respond with ``{"server": {"id": "12345"}}``. In this case,
* "server" is the resource key, since the essential state of the server is nested inside.
*
* @var string
*/
protected $resourceKey;
/**
* An array of aliases that will be checked when the resource is being populated. For example,.
*
* 'FOO_BAR' => 'fooBar'
*
* will extract FOO_BAR from the response, and save it as 'fooBar' in the resource.
*
* @var array
*/
protected $aliases = [];
/**
* Populates the current resource from a response object.
*
* @return AbstractResource
*/
public function populateFromResponse(ResponseInterface $response)
{
if (0 === strpos($response->getHeaderLine('Content-Type'), 'application/json')) {
$json = Utils::jsonDecode($response);
if (!empty($json)) {
$this->populateFromArray(Utils::flattenJson($json, $this->resourceKey));
}
}
return $this;
}
/**
* Populates the current resource from a data array.
*
* @return self
*/
public function populateFromArray(array $array)
{
$aliases = $this->getAliases();
foreach ($array as $key => $val) {
$alias = $aliases[$key] ?? false;
if ($alias instanceof Alias) {
$key = $alias->propertyName;
$val = $alias->getValue($this, $val);
}
if (property_exists($this, $key)) {
$this->{$key} = $val;
}
}
return $this;
}
/**
* Constructs alias objects.
*
* @return Alias[]
*/
protected function getAliases(): array
{
$aliases = [];
foreach ((array) $this->aliases as $alias => $property) {
$aliases[$alias] = new Alias($property);
}
return $aliases;
}
/**
* Internal method which retrieves the values of provided keys.
*
* @return array
*/
protected function getAttrs(array $keys)
{
$output = [];
foreach ($keys as $key) {
if (property_exists($this, $key) && $this->$key !== null) {
$output[$key] = $this->$key;
}
}
return $output;
}
public function model(string $class, $data = null): ResourceInterface
{
$model = new $class();
// @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;
}
public function serialize(): \stdClass
{
$output = new \stdClass();
foreach ((new \ReflectionClass($this))->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
$name = $property->getName();
$val = $this->{$name};
$fn = function ($val) {
if ($val instanceof Serializable) {
return $val->serialize();
} elseif ($val instanceof \DateTimeImmutable) {
return $val->format('c');
} else {
return $val;
}
};
if (is_array($val)) {
foreach ($val as $sk => $sv) {
$val[$sk] = $fn($sv);
}
}
$output->{$name} = $fn($val);
}
return $output;
}
}
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Resource;
class Alias
{
/**
* @var string
*/
public $propertyName;
/**
* @var bool
*/
private $isList;
/**
* @var string
*/
private $className;
/**
* @param string $propertyName A name of the property in target resource class
* @param string|null $className A class name for the property value
* @param bool $list Whether value of the property should be treated as a list or not
*/
public function __construct(string $propertyName, ?string $className = null, bool $list = false)
{
$this->isList = $list;
$this->propertyName = $propertyName;
$this->className = $className && class_exists($className) ? $className : null;
}
public function getValue(ResourceInterface $resource, $value)
{
if (null === $value || !$this->className) {
return $value;
} elseif ($this->isList && is_array($value)) {
$array = [];
foreach ($value as $subVal) {
$array[] = $resource->model($this->className, $subVal);
}
return $array;
} elseif (\DateTimeImmutable::class === $this->className) {
return new \DateTimeImmutable($value);
}
return $resource->model($this->className, $value);
}
}
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Resource;
/**
* Represents a resource that can be created.
*/
interface Creatable
{
/**
* Create a new resource according to the configuration set in the options.
*
* @return static
*/
public function create(array $userOptions): Creatable;
}
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Resource;
/**
* Represents a resource that can be deleted.
*/
interface Deletable
{
/**
* Permanently delete this resource.
*/
public function delete();
}
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Resource;
use Psr\Http\Message\ResponseInterface;
interface HasMetadata
{
/**
* Retrieves the metadata for the resource in the form of an associative array or hash. Each key represents the
* metadata item's name, and each value represents the metadata item's remote value.
*/
public function getMetadata(): array;
/**
* Merges a set of new values with those which already exist (on the remote API) for a resource. For example, if
* the resource has this metadata already set:.
*
* Foo: val1
* Bar: val2
*
* and mergeMetadata(['Foo' => 'val3', 'Baz' => 'val4']); is called, then the resource will have the following
* metadata:
*
* Foo: val3
* Bar: val2
* Baz: val4
*
* You will notice that any metadata items which are not specified in the call are preserved.
*
* @param array $metadata The new metadata items
*/
public function mergeMetadata(array $metadata);
/**
* Replaces all of the existing metadata items for a resource with a new set of values. Any metadata items which
* are not provided in the call are removed from the resource. For example, if the resource has this metadata
* already set:.
*
* Foo: val1
* Bar: val2
*
* and resetMetadata(['Foo' => 'val3', 'Baz' => 'val4']); is called, then the resource will have the following
* metadata:
*
* Foo: val3
* Baz: val4
*
* @param array $metadata The new metadata items
*/
public function resetMetadata(array $metadata);
/**
* Extracts metadata from a response object and returns it in the form of an associative array.
*/
public function parseMetadata(ResponseInterface $response): array;
}
@@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Resource;
use OpenStack\Common\Error\BadResponseError;
/**
* Contains reusable functionality for resources that have long operations which require waiting in
* order to reach a particular state.
*
* @codeCoverageIgnore
*/
trait HasWaiterTrait
{
/**
* Provides a blocking operation until the resource has reached a particular state. The method
* will enter a loop, requesting feedback from the remote API until it sends back an appropriate
* status.
*
* @param string $status The state to be reached
* @param bool|int $timeout The maximum timeout. If the total time taken by the waiter has reached
* or exceed this timeout, the blocking operation will immediately cease.
* @param int $sleepPeriod the amount of time to pause between each HTTP request
*/
public function waitUntil(string $status, $timeout = 60, int $sleepPeriod = 1)
{
$startTime = time();
while (true) {
$this->retrieve();
if ($this->status == $status || $this->shouldHalt($timeout, $startTime)) {
break;
}
sleep($sleepPeriod);
}
}
/**
* Provides a blocking operation until the resource has reached a particular state. The method
* will enter a loop, executing the callback until TRUE is returned. This provides great
* flexibility.
*
* @param callable $fn An anonymous function that will be executed on every iteration. You can
* encapsulate your own logic to determine whether the resource has
* successfully transitioned. When TRUE is returned by the callback,
* the loop will end.
* @param int|bool $timeout The maximum timeout in seconds. If the total time taken by the waiter has reached
* or exceed this timeout, the blocking operation will immediately cease. If FALSE
* is provided, the timeout will never be considered.
* @param int $sleepPeriod the amount of time to pause between each HTTP request
*/
public function waitWithCallback(callable $fn, $timeout = 60, int $sleepPeriod = 1)
{
$startTime = time();
while (true) {
$this->retrieve();
$response = call_user_func_array($fn, [$this]);
if (true === $response || $this->shouldHalt($timeout, $startTime)) {
break;
}
sleep($sleepPeriod);
}
}
/**
* Internal method used to identify whether a timeout has been exceeded.
*
* @param bool|int $timeout
*
* @return bool
*/
private function shouldHalt($timeout, int $startTime)
{
if (false === $timeout) {
return false;
}
return time() - $startTime >= $timeout;
}
/**
* Convenience method providing a blocking operation until the resource transitions to an
* ``ACTIVE`` status.
*
* @param int|bool $timeout The maximum timeout in seconds. If the total time taken by the waiter has reached
* or exceed this timeout, the blocking operation will immediately cease. If FALSE
* is provided, the timeout will never be considered.
*/
public function waitUntilActive($timeout = false)
{
$this->waitUntil('ACTIVE', $timeout);
}
public function waitUntilDeleted($timeout = 60, int $sleepPeriod = 1)
{
$startTime = time();
while (true) {
try {
$this->retrieve();
} catch (BadResponseError $e) {
if (404 === $e->getResponse()->getStatusCode()) {
break;
}
throw $e;
}
if ($this->shouldHalt($timeout, $startTime)) {
break;
}
sleep($sleepPeriod);
}
}
}
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Resource;
use OpenStack\Common\Transport\Utils;
class Iterator
{
private $requestFn;
private $resourceFn;
private $limit;
private $count;
private $resourcesKey;
private $markerKey;
private $mapFn;
private $currentMarker;
public function __construct(array $options, callable $requestFn, callable $resourceFn)
{
$this->limit = isset($options['limit']) ? $options['limit'] : false;
$this->count = 0;
if (isset($options['resourcesKey'])) {
$this->resourcesKey = $options['resourcesKey'];
}
if (isset($options['markerKey'])) {
$this->markerKey = $options['markerKey'];
}
if (isset($options['mapFn']) && is_callable($options['mapFn'])) {
$this->mapFn = $options['mapFn'];
}
$this->requestFn = $requestFn;
$this->resourceFn = $resourceFn;
}
private function fetchResources()
{
if ($this->shouldNotSendAnotherRequest()) {
return false;
}
$response = call_user_func($this->requestFn, $this->currentMarker);
$json = Utils::flattenJson(Utils::jsonDecode($response), $this->resourcesKey);
if (204 === $response->getStatusCode() || empty($json)) {
return false;
}
return $json;
}
private function assembleResource(array $data)
{
$resource = call_user_func($this->resourceFn, $data);
// Invoke user-provided fn if provided
if ($this->mapFn) {
call_user_func_array($this->mapFn, [&$resource]);
}
// Update marker if operation supports it
if ($this->markerKey) {
$this->currentMarker = $resource->{$this->markerKey};
}
return $resource;
}
private function totalReached()
{
return $this->limit && $this->count >= $this->limit;
}
private function shouldNotSendAnotherRequest()
{
return $this->totalReached() || ($this->count > 0 && !$this->markerKey);
}
public function __invoke()
{
while ($resources = $this->fetchResources()) {
foreach ($resources as $resourceData) {
if ($this->totalReached()) {
break;
}
++$this->count;
yield $this->assembleResource($resourceData);
}
}
}
}
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Resource;
/**
* Represents a resource that can be enumerated (listed over).
*/
interface Listable
{
/**
* This method iterates over a collection of resources. It sends the operation's request to the API,
* parses the response, converts each element into {@see self} and - if pagination is supported - continues
* to send requests until an empty collection is received back.
*
* For paginated collections, it sends subsequent requests according to a marker URL query. The value
* of the marker will depend on the last element returned in the previous response. If a limit is
* provided, the loop will continue up until that point.
*
* @param array $def The operation definition
* @param array $userVals The user values
* @param callable|null $mapFn an optional callback that will be executed on every resource iteration
*
* @returns \Generator<mixed, static>
*/
public function enumerate(array $def, array $userVals = [], ?callable $mapFn = null);
}
@@ -0,0 +1,148 @@
<?php
namespace OpenStack\Common\Resource;
use OpenStack\Common\Api\OperatorInterface;
use OpenStack\Common\Api\OperatorTrait;
use OpenStack\Common\Transport\Utils;
use Psr\Http\Message\ResponseInterface;
abstract class OperatorResource extends AbstractResource implements OperatorInterface
{
use OperatorTrait;
public const DEFAULT_MARKER_KEY = 'id';
/**
* The key that indicates how the API nests resource collections. For example, when
* performing a GET, it could respond with ``{"servers": [{}, {}]}``. In this case, "servers"
* is the resources key, since the array of servers is nested inside.
*
* @var string
*/
protected $resourcesKey;
/**
* Indicates which attribute of the current resource should be used for pagination markers.
*
* @var string
*/
protected $markerKey;
/**
* Will create a new instance of this class with the current HTTP client and API injected in. This
* is useful when enumerating over a collection since multiple copies of the same resource class
* are needed.
*/
public function newInstance(): OperatorResource
{
return new static($this->client, $this->api);
}
/**
* @return \GuzzleHttp\Psr7\Uri:null
*/
protected function getHttpBaseUrl()
{
return $this->client->getConfig('base_uri');
}
public function executeWithState(array $definition)
{
return $this->execute($definition, $this->getAttrs(array_keys($definition['params'])));
}
private function getResourcesKey(): string
{
$resourcesKey = $this->resourcesKey;
if (!$resourcesKey) {
$class = substr(static::class, strrpos(static::class, '\\') + 1);
$resourcesKey = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $class)).'s';
}
return $resourcesKey;
}
/**
* Creates a generator for enumerating over a collection of resources returned by the request.
*
* @returns \Generator<mixed, static>
*/
public function enumerate(array $def, array $userVals = [], ?callable $mapFn = null): \Generator
{
$operation = $this->getOperation($def);
$requestFn = function ($marker) use ($operation, $userVals) {
if ($marker) {
$userVals['marker'] = $marker;
}
return $this->sendRequest($operation, $userVals);
};
$resourceFn = function (array $data) {
$resource = $this->newInstance();
$resource->populateFromArray($data);
return $resource;
};
$opts = [
'limit' => isset($userVals['limit']) ? $userVals['limit'] : null,
'resourcesKey' => $this->getResourcesKey(),
'markerKey' => $this->markerKey,
'mapFn' => $mapFn,
];
$iterator = new Iterator($opts, $requestFn, $resourceFn);
return $iterator();
}
/**
* Extracts multiple instances of the current resource from a response.
*
* @return array<self>
*/
public function extractMultipleInstances(ResponseInterface $response, ?string $key = null): array
{
$key = $key ?: $this->getResourcesKey();
$resourcesData = Utils::jsonDecode($response)[$key];
$resources = [];
foreach ($resourcesData as $resourceData) {
$resources[] = $this->newInstance()->populateFromArray($resourceData);
}
return $resources;
}
protected function getService()
{
$class = static::class;
$service = substr($class, 0, strpos($class, 'Models') - 1).'\\Service';
return new $service($this->client, $this->api);
}
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,37 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Resource;
use Psr\Http\Message\ResponseInterface;
/**
* Represents an API resource.
*/
interface ResourceInterface
{
/**
* All models which represent an API resource should be able to be populated
* from a {@see ResponseInterface} object.
*
* @return self
*/
public function populateFromResponse(ResponseInterface $response);
/**
* @return self
*/
public function populateFromArray(array $data);
/**
* @template T of \OpenStack\Common\Resource\ResourceInterface
*
* @param class-string<T> $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
*
* @return T
*/
public function model(string $class, $data = null): ResourceInterface;
}
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Resource;
/**
* A resource that supports a GET or HEAD operation to retrieve more details.
*/
interface Retrievable
{
/**
* Retrieve details of the current resource from the remote API.
*/
public function retrieve();
}
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Resource;
/**
* Represents a resource that can be updated.
*/
interface Updateable
{
/**
* Update the current resource with the configuration set out in the user options.
*/
public function update();
}
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Service;
use OpenStack\Common\Api\OperatorTrait;
/**
* Represents the top-level abstraction of a service.
*/
abstract class AbstractService implements ServiceInterface
{
use OperatorTrait;
}
@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Service;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\HandlerStack;
use OpenStack\Common\Auth\IdentityService;
use OpenStack\Common\Transport\HandlerStackFactory;
use OpenStack\Common\Transport\Utils;
/**
* A Builder for easily creating OpenStack services.
*/
class Builder
{
/**
* Global options that will be applied to every service created by this builder.
*
* @var array
*/
private $globalOptions = [];
/** @var string */
private $rootNamespace;
/**
* Defaults that will be applied to options if no values are provided by the user.
*
* @var array
*/
private $defaults = ['urlType' => 'publicURL'];
/**
* @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
*/
public function __construct(array $globalOptions = [], $rootNamespace = 'OpenStack')
{
$this->globalOptions = $globalOptions;
$this->rootNamespace = $rootNamespace;
}
private function getClasses($namespace)
{
$namespace = $this->rootNamespace.'\\'.$namespace;
$classes = [$namespace.'\\Api', $namespace.'\\Service'];
foreach ($classes as $class) {
if (!class_exists($class)) {
throw new \RuntimeException(sprintf('%s does not exist', $class));
}
}
return $classes;
}
/**
* This method will return an OpenStack service ready fully built and ready for use. There is
* some initial setup that may prohibit users from directly instantiating the service class
* 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
*/
public function createService(string $namespace, array $serviceOptions = []): ServiceInterface
{
$options = $this->mergeOptions($serviceOptions);
$this->stockAuthHandler($options);
$this->stockHttpClient($options, $namespace);
[$apiClass, $serviceClass] = $this->getClasses($namespace);
return new $serviceClass($options['httpClient'], new $apiClass());
}
private function stockHttpClient(array &$options, string $serviceName): void
{
if (!isset($options['httpClient']) || !($options['httpClient'] instanceof ClientInterface)) {
if (false !== stripos($serviceName, 'identity')) {
$baseUrl = $options['authUrl'];
$token = null;
} else {
[$token, $baseUrl] = $options['identityService']->authenticate($options);
}
$stack = HandlerStackFactory::createWithOptions(array_merge($options, ['token' => $token]));
$microVersion = $options['microVersion'] ?? null;
$options['httpClient'] = $this->httpClient($baseUrl, $stack, $options['catalogType'], $microVersion);
}
}
/**
* @codeCoverageIgnore
*/
private function stockAuthHandler(array &$options): void
{
if (!isset($options['authHandler'])) {
$options['authHandler'] = function () use ($options) {
return $options['identityService']->authenticate($options)[0];
};
}
}
private function httpClient(string $baseUrl, HandlerStack $stack, ?string $serviceType = null, ?string $microVersion = null): ClientInterface
{
$clientOptions = [
'base_uri' => Utils::normalizeUrl($baseUrl),
'handler' => $stack,
];
if ($microVersion && $serviceType) {
$clientOptions['headers']['OpenStack-API-Version'] = sprintf('%s %s', $serviceType, $microVersion);
}
if (isset($this->globalOptions['requestOptions'])) {
$clientOptions = array_merge($this->globalOptions['requestOptions'], $clientOptions);
}
return new Client($clientOptions);
}
private function mergeOptions(array $serviceOptions): array
{
$options = array_merge($this->defaults, $this->globalOptions, $serviceOptions);
if (!isset($options['authUrl'])) {
throw new \InvalidArgumentException('"authUrl" is a required option');
}
if (!isset($options['identityService']) || !($options['identityService'] instanceof IdentityService)) {
throw new \InvalidArgumentException(sprintf('"identityService" must be specified and implement %s', IdentityService::class));
}
return $options;
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Service;
use OpenStack\Common\Api\OperatorInterface;
/**
* Service interface.
*/
interface ServiceInterface extends OperatorInterface
{
}
@@ -0,0 +1,17 @@
<?php
namespace OpenStack\Common\Transport;
/**
* @deprecated use \OpenStack\Common\Transport\HandlerStackFactory instead
*/
class HandlerStack
{
/**
* @deprecated use \OpenStack\Common\Transport\HandlerStackFactory::createWithOptions instead
*/
public static function create(?callable $handler = null): \GuzzleHttp\HandlerStack
{
return HandlerStackFactory::create($handler);
}
}
@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
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());
$stack->push(Middleware::httpErrors(), 'http_errors');
$stack->push(Middleware::prepareBody(), 'prepare_body');
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;
}
}
@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Transport;
use OpenStack\Common\Api\Parameter;
use OpenStack\Common\JsonPath;
/**
* Class responsible for populating the JSON body of a {@see GuzzleHttp\Message\Request} object.
*/
class JsonSerializer
{
/**
* Populates the actual value into a JSON field, i.e. it has reached the end of the line and no
* further nesting is required.
*
* @param Parameter $param The schema that defines how the JSON field is being populated
* @param mixed $userValue The user value that is populating a JSON field
* @param array $json The existing JSON structure that will be populated
*
* @return array|mixed
*/
private function stockValue(Parameter $param, $userValue, array $json): array
{
$name = $param->getName();
if ($path = $param->getPath()) {
$jsonPath = new JsonPath($json);
$jsonPath->set(sprintf('%s.%s', $path, $name), $userValue);
$json = $jsonPath->getStructure();
} elseif ($name) {
$json[$name] = $userValue;
} else {
$json[] = $userValue;
}
return $json;
}
/**
* Populates a value into an array-like structure.
*
* @param Parameter $param The schema that defines how the JSON field is being populated
* @param mixed $userValue The user value that is populating a JSON field
*
* @return array|mixed
*/
private function stockArrayJson(Parameter $param, array $userValue): array
{
$elems = [];
foreach ($userValue as $item) {
$elems = $this->stockJson($param->getItemSchema(), $item, $elems);
}
return $elems;
}
/**
* Populates a value into an object-like structure.
*
* @param Parameter $param The schema that defines how the JSON field is being populated
* @param mixed $userValue The user value that is populating a JSON field
*/
private function stockObjectJson(Parameter $param, \stdClass $userValue): array
{
$object = [];
foreach ($userValue as $key => $val) {
$object = $this->stockJson($param->getProperty($key), $val, $object);
}
return $object;
}
/**
* A generic method that will populate a JSON structure with a value according to a schema. It
* supports multiple types and will delegate accordingly.
*
* @param Parameter $param The schema that defines how the JSON field is being populated
* @param mixed $userValue The user value that is populating a JSON field
* @param array $json The existing JSON structure that will be populated
*/
public function stockJson(Parameter $param, $userValue, array $json): array
{
if ($param->isArray()) {
$userValue = $this->stockArrayJson($param, $userValue);
} elseif ($param->isObject()) {
$userValue = $this->stockObjectJson($param, $this->serializeObjectValue($userValue));
}
// Populate the final value
return $this->stockValue($param, $userValue, $json);
}
private function serializeObjectValue($value)
{
if (is_object($value)) {
if ($value instanceof Serializable) {
$value = $value->serialize();
} elseif (!($value instanceof \stdClass)) {
throw new \InvalidArgumentException(sprintf('When an object value is provided, it must either be \stdClass or implement the Serializable interface, you provided %s', print_r($value, true)));
}
}
return (object) $value;
}
}
@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Transport;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware as GuzzleMiddleware;
use OpenStack\Common\Auth\AuthHandler;
use OpenStack\Common\Auth\Token;
use OpenStack\Common\Error\Builder;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
final class Middleware
{
public static function httpErrors(int $verbosity = 0): callable
{
return function (callable $handler) use ($verbosity) {
return function ($request, array $options) use ($handler, $verbosity) {
return $handler($request, $options)->then(
function (ResponseInterface $response) use ($request, $verbosity) {
if ($response->getStatusCode() < 400) {
return $response;
}
throw (new Builder())->httpError($request, $response, $verbosity);
}
);
};
};
}
public static function authHandler(callable $tokenGenerator, ?Token $token = null): callable
{
return function (callable $handler) use ($tokenGenerator, $token) {
return new AuthHandler($handler, $tokenGenerator, $token);
};
}
/**
* @codeCoverageIgnore
*/
public static function history(array &$container): callable
{
return GuzzleMiddleware::history($container);
}
/**
* @codeCoverageIgnore
*/
public static function retry(callable $decider, ?callable $delay = null): callable
{
return GuzzleMiddleware::retry($decider, $delay);
}
/**
* @codeCoverageIgnore
*/
public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = LogLevel::INFO): callable
{
return GuzzleMiddleware::log($logger, $formatter, $logLevel);
}
/**
* @codeCoverageIgnore
*/
public static function prepareBody(): callable
{
return GuzzleMiddleware::prepareBody();
}
/**
* @codeCoverageIgnore
*/
public static function mapRequest(callable $fn): callable
{
return GuzzleMiddleware::mapRequest($fn);
}
/**
* @codeCoverageIgnore
*/
public static function mapResponse(callable $fn): callable
{
return GuzzleMiddleware::mapResponse($fn);
}
}
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Transport;
use OpenStack\Common\Api\Operation;
use OpenStack\Common\Api\Parameter;
class RequestSerializer
{
private $jsonSerializer;
public function __construct(?JsonSerializer $jsonSerializer = null)
{
$this->jsonSerializer = $jsonSerializer ?: new JsonSerializer();
}
public function serializeOptions(Operation $operation, array $userValues = []): array
{
$options = ['headers' => []];
foreach ($userValues as $paramName => $paramValue) {
if (null === ($schema = $operation->getParam($paramName))) {
continue;
}
$this->callStockingMethod($schema, $paramValue, $options);
}
if (!empty($options['json'])) {
if ($key = $operation->getJsonKey()) {
$options['json'] = [$key => $options['json']];
}
if (false !== strpos(json_encode($options['json']), '\/')) {
$options['body'] = json_encode($options['json'], JSON_UNESCAPED_SLASHES);
$options['headers']['Content-Type'] = 'application/json';
unset($options['json']);
}
}
return $options;
}
private function callStockingMethod(Parameter $schema, $paramValue, array &$options)
{
$location = $schema->getLocation();
$methods = ['query', 'header', 'json', 'raw'];
if (!in_array($location, $methods)) {
return;
}
$method = sprintf('stock%s', ucfirst($location));
$this->$method($schema, $paramValue, $options);
}
private function stockQuery(Parameter $schema, $paramValue, array &$options)
{
$options['query'][$schema->getName()] = $paramValue;
}
private function stockHeader(Parameter $schema, $paramValue, array &$options)
{
$paramName = $schema->getName();
if (false !== stripos($paramName, 'metadata')) {
return $this->stockMetadataHeader($schema, $paramValue, $options);
}
$options['headers'] += is_scalar($paramValue) ? [$schema->getPrefixedName() => $paramValue] : [];
}
private function stockMetadataHeader(Parameter $schema, $paramValue, array &$options)
{
foreach ($paramValue as $key => $keyVal) {
$schema = $schema->getItemSchema() ?: new Parameter(['prefix' => $schema->getPrefix(), 'name' => $key]);
$this->stockHeader($schema, $keyVal, $options);
}
}
private function stockJson(Parameter $schema, $paramValue, array &$options)
{
$json = isset($options['json']) ? $options['json'] : [];
$options['json'] = $this->jsonSerializer->stockJson($schema, $paramValue, $json);
}
private function stockRaw(Parameter $schema, $paramValue, array &$options)
{
$options['body'] = $paramValue;
}
}
@@ -0,0 +1,11 @@
<?php
namespace OpenStack\Common\Transport;
interface Serializable
{
/**
* @return string
*/
public function serialize(): \stdClass;
}
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace OpenStack\Common\Transport;
use GuzzleHttp\Psr7\Utils as GuzzleUtils;
use GuzzleHttp\UriTemplate\UriTemplate;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
class Utils
{
public static function jsonDecode(ResponseInterface $response, bool $assoc = true)
{
$jsonErrors = [
JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded',
JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch',
JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found',
JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON',
JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded',
];
$responseBody = (string) $response->getBody();
if (0 === strlen($responseBody)) {
return $responseBody;
}
$data = json_decode($responseBody, $assoc);
if (JSON_ERROR_NONE !== json_last_error()) {
$last = json_last_error();
throw new \InvalidArgumentException('Unable to parse JSON data: '.(isset($jsonErrors[$last]) ? $jsonErrors[$last] : 'Unknown error'));
}
return $data;
}
/**
* Method for flattening a nested array.
*
* @param array $data The nested array
* @param string $key The key to extract
*
* @return array
*/
public static function flattenJson($data, ?string $key = null)
{
return (!empty($data) && $key && isset($data[$key])) ? $data[$key] : $data;
}
/**
* Method for normalize an URL string.
*
* Append the http:// prefix if not present, and add a
* closing url separator when missing.
*
* @param string $url the url representation
*/
public static function normalizeUrl(string $url): string
{
if (false === strpos($url, 'http')) {
$url = 'http://'.$url;
}
return rtrim($url, '/').'/';
}
/**
* Add an unlimited list of paths to a given URI.
*/
public static function addPaths(UriInterface $uri, ...$paths): UriInterface
{
return GuzzleUtils::uriFor(rtrim((string) $uri, '/').'/'.implode('/', $paths));
}
public static function appendPath(UriInterface $uri, $path): UriInterface
{
return GuzzleUtils::uriFor(rtrim((string) $uri, '/').'/'.$path);
}
/**
* Expands a URI template.
*
* @param string $template URI template
* @param array $variables Template variables
*/
public static function uri_template($template, array $variables): string
{
if (extension_loaded('uri_template')) {
// @codeCoverageIgnoreStart
return \uri_template($template, $variables);
// @codeCoverageIgnoreEnd
}
return UriTemplate::expand($template, $variables);
}
}
+916
View File
@@ -0,0 +1,916 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2;
use OpenStack\Common\Api\AbstractApi;
/**
* A representation of the Compute (Nova) v2 REST API.
*
* @internal
*/
class Api extends AbstractApi
{
public function __construct()
{
$this->params = new Params();
}
public function getLimits(): array
{
return [
'method' => 'GET',
'path' => 'limits',
'params' => [],
];
}
public function getFlavors(): array
{
return [
'method' => 'GET',
'path' => 'flavors',
'params' => [
'limit' => $this->params->limit(),
'marker' => $this->params->marker(),
'minDisk' => $this->params->minDisk(),
'minRam' => $this->params->minRam(),
],
];
}
public function getFlavorsDetail(): array
{
$op = $this->getFlavors();
$op['path'] .= '/detail';
return $op;
}
public function getFlavor(): array
{
return [
'method' => 'GET',
'path' => 'flavors/{id}',
'params' => ['id' => $this->params->urlId('flavor')],
];
}
public function postFlavors(): array
{
return [
'method' => 'POST',
'path' => 'flavors',
'jsonKey' => 'flavor',
'params' => [
'id' => $this->notRequired($this->params->id('flavor')),
'name' => $this->isRequired($this->params->name('flavor')),
'ram' => $this->params->flavorRam(),
'vcpus' => $this->params->flavorVcpus(),
'swap' => $this->params->flavorSwap(),
'disk' => $this->params->flavorDisk(),
],
];
}
public function deleteFlavor(): array
{
return [
'method' => 'DELETE',
'path' => 'flavors/{id}',
'params' => [
'id' => $this->params->idPath(),
],
];
}
public function getImages(): array
{
return [
'method' => 'GET',
'path' => 'images',
'params' => [
'limit' => $this->params->limit(),
'marker' => $this->params->marker(),
'name' => $this->params->flavorName(),
'changesSince' => $this->params->filterChangesSince('image'),
'server' => $this->params->flavorServer(),
'status' => $this->params->filterStatus('image'),
'type' => $this->params->flavorType(),
],
];
}
public function getImagesDetail(): array
{
$op = $this->getImages();
$op['path'] .= '/detail';
return $op;
}
public function getImage(): array
{
return [
'method' => 'GET',
'path' => 'images/{id}',
'params' => ['id' => $this->params->urlId('image')],
];
}
public function deleteImage(): array
{
return [
'method' => 'DELETE',
'path' => 'images/{id}',
'params' => ['id' => $this->params->urlId('image')],
];
}
public function getImageMetadata(): array
{
return [
'method' => 'GET',
'path' => 'images/{id}/metadata',
'params' => ['id' => $this->params->urlId('image')],
];
}
public function putImageMetadata(): array
{
return [
'method' => 'PUT',
'path' => 'images/{id}/metadata',
'params' => [
'id' => $this->params->urlId('image'),
'metadata' => $this->params->metadata(),
],
];
}
public function postImageMetadata(): array
{
return [
'method' => 'POST',
'path' => 'images/{id}/metadata',
'params' => [
'id' => $this->params->urlId('image'),
'metadata' => $this->params->metadata(),
],
];
}
public function getImageMetadataKey(): array
{
return [
'method' => 'GET',
'path' => 'images/{id}/metadata/{key}',
'params' => [
'id' => $this->params->urlId('image'),
'key' => $this->params->key(),
],
];
}
public function deleteImageMetadataKey(): array
{
return [
'method' => 'DELETE',
'path' => 'images/{id}/metadata/{key}',
'params' => [
'id' => $this->params->urlId('image'),
'key' => $this->params->key(),
],
];
}
public function postServer(): array
{
return [
'path' => 'servers',
'method' => 'POST',
'jsonKey' => 'server',
'params' => [
'imageId' => $this->notRequired($this->params->imageId()),
'flavorId' => $this->params->flavorId(),
'personality' => $this->params->personality(),
'metadata' => $this->notRequired($this->params->metadata()),
'name' => $this->isRequired($this->params->name('server')),
'securityGroups' => $this->params->securityGroups(),
'userData' => $this->params->userData(),
'availabilityZone' => $this->params->availabilityZone(),
'networks' => $this->params->networks(),
'blockDeviceMapping' => $this->params->blockDeviceMapping(),
'keyName' => $this->params->keyName(),
],
];
}
public function getServers(): array
{
return [
'method' => 'GET',
'path' => 'servers',
'params' => [
'limit' => $this->params->limit(),
'marker' => $this->params->marker(),
'changesSince' => $this->params->filterChangesSince('server'),
'imageId' => $this->params->filterImage(),
'flavorId' => $this->params->filterFlavor(),
'name' => $this->params->filterName(),
'status' => $this->params->filterStatus('server'),
'host' => $this->params->filterHost(),
'allTenants' => $this->params->allTenants(),
],
];
}
public function getServersDetail(): array
{
$definition = $this->getServers();
$definition['path'] .= '/detail';
return $definition;
}
public function getServer(): array
{
return [
'method' => 'GET',
'path' => 'servers/{id}',
'params' => [
'id' => $this->params->urlId('server'),
],
];
}
public function putServer(): array
{
return [
'method' => 'PUT',
'path' => 'servers/{id}',
'jsonKey' => 'server',
'params' => [
'id' => $this->params->urlId('server'),
'ipv4' => $this->params->ipv4(),
'ipv6' => $this->params->ipv6(),
'name' => $this->params->name('server'),
],
];
}
public function deleteServer(): array
{
return [
'method' => 'DELETE',
'path' => 'servers/{id}',
'params' => ['id' => $this->params->urlId('server')],
];
}
public function changeServerPassword(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'jsonKey' => 'changePassword',
'params' => [
'id' => $this->params->urlId('server'),
'password' => $this->params->password(),
],
];
}
public function resetServerState(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'params' => [
'id' => $this->params->urlId('server'),
'resetState' => $this->params->resetState(),
],
];
}
public function rebootServer(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'jsonKey' => 'reboot',
'params' => [
'id' => $this->params->urlId('server'),
'type' => $this->params->rebootType(),
],
];
}
public function startServer(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'params' => [
'id' => $this->params->urlId('server'),
'os-start' => $this->params->nullAction(),
],
];
}
public function stopServer(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'params' => [
'id' => $this->params->urlId('server'),
'os-stop' => $this->params->nullAction(),
],
];
}
public function resumeServer(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'params' => [
'id' => $this->params->urlId('server'),
'resume' => $this->params->nullAction(),
],
];
}
public function suspendServer(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'params' => [
'id' => $this->params->urlId('server'),
'suspend' => $this->params->nullAction(),
],
];
}
public function rebuildServer(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'jsonKey' => 'rebuild',
'params' => [
'id' => $this->params->urlId('server'),
'ipv4' => $this->params->ipv4(),
'ipv6' => $this->params->ipv6(),
'imageId' => $this->params->imageId(),
'personality' => $this->params->personality(),
'name' => $this->params->name('server'),
'metadata' => $this->notRequired($this->params->metadata()),
'adminPass' => $this->params->password(),
],
];
}
public function rescueServer(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'jsonKey' => 'rescue',
'params' => [
'id' => $this->params->urlId('server'),
'imageId' => $this->params->rescueImageId(),
'adminPass' => $this->notRequired($this->params->password()),
],
];
}
public function unrescueServer(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'params' => [
'id' => $this->params->urlId('server'),
'unrescue' => $this->params->nullAction(),
],
];
}
public function resizeServer(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'jsonKey' => 'resize',
'params' => [
'id' => $this->params->urlId('server'),
'flavorId' => $this->params->flavorId(),
],
];
}
public function confirmServerResize(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'params' => [
'id' => $this->params->urlId('server'),
'confirmResize' => $this->params->nullAction(),
],
];
}
public function revertServerResize(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'params' => [
'id' => $this->params->urlId('server'),
'revertResize' => $this->params->nullAction(),
],
];
}
public function getConsoleOutput(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'jsonKey' => 'os-getConsoleOutput',
'params' => [
'id' => $this->params->urlId('server'),
'length' => $this->notRequired($this->params->consoleLogLength()),
],
];
}
public function getAllConsoleOutput(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'params' => [
'id' => $this->params->urlId('server'),
'os-getConsoleOutput' => $this->params->emptyObject(),
],
];
}
public function createServerImage(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'jsonKey' => 'createImage',
'params' => [
'id' => $this->params->urlId('server'),
'metadata' => $this->notRequired($this->params->metadata()),
'name' => $this->isRequired($this->params->name('server')),
],
];
}
public function getVncConsole(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'jsonKey' => 'os-getVNCConsole',
'params' => [
'id' => $this->params->urlId('server'),
'type' => $this->params->consoleType(),
],
];
}
public function getSpiceConsole(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'jsonKey' => 'os-getSPICEConsole',
'params' => [
'id' => $this->params->urlId('server'),
'type' => $this->params->consoleType(),
],
];
}
public function getSerialConsole(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'jsonKey' => 'os-getSerialConsole',
'params' => [
'id' => $this->params->urlId('server'),
'type' => $this->params->consoleType(),
],
];
}
public function getRDPConsole(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'jsonKey' => 'os-getRDPConsole',
'params' => [
'id' => $this->params->urlId('server'),
'type' => $this->params->consoleType(),
],
];
}
public function getAddresses(): array
{
return [
'method' => 'GET',
'path' => 'servers/{id}/ips',
'params' => ['id' => $this->params->urlId('server')],
];
}
public function getAddressesByNetwork(): array
{
return [
'method' => 'GET',
'path' => 'servers/{id}/ips/{networkLabel}',
'params' => [
'id' => $this->params->urlId('server'),
'networkLabel' => $this->params->networkLabel(),
],
];
}
public function getInterfaceAttachments(): array
{
return [
'method' => 'GET',
'path' => 'servers/{id}/os-interface',
'jsonKey' => 'interfaceAttachments',
'params' => [
'id' => $this->params->urlId('server'),
],
];
}
public function getInterfaceAttachment(): array
{
return [
'method' => 'GET',
'path' => 'servers/{id}/os-interface/{portId}',
'params' => [
'id' => $this->params->urlId('server'),
'portId' => $this->params->portId(),
],
];
}
public function postInterfaceAttachment(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/os-interface',
'jsonKey' => 'interfaceAttachment',
'params' => [
'id' => $this->params->urlId('server'),
'portId' => $this->notRequired($this->params->portId()),
'networkId' => $this->notRequired($this->params->networkId()),
'fixedIpAddresses' => $this->notRequired($this->params->fixedIpAddresses()),
'tag' => $this->notRequired($this->params->tag()),
],
];
}
public function deleteInterfaceAttachment(): array
{
return [
'method' => 'DELETE',
'path' => 'servers/{id}/os-interface/{portId}',
'params' => [
'id' => $this->params->urlId('image'),
'portId' => $this->params->portId(),
],
];
}
public function getServerMetadata(): array
{
return [
'method' => 'GET',
'path' => 'servers/{id}/metadata',
'params' => ['id' => $this->params->urlId('server')],
];
}
public function putServerMetadata(): array
{
return [
'method' => 'PUT',
'path' => 'servers/{id}/metadata',
'params' => [
'id' => $this->params->urlId('server'),
'metadata' => $this->params->metadata(),
],
];
}
public function postServerMetadata(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/metadata',
'params' => [
'id' => $this->params->urlId('server'),
'metadata' => $this->params->metadata(),
],
];
}
public function getServerMetadataKey(): array
{
return [
'method' => 'GET',
'path' => 'servers/{id}/metadata/{key}',
'params' => [
'id' => $this->params->urlId('server'),
'key' => $this->params->key(),
],
];
}
public function deleteServerMetadataKey(): array
{
return [
'method' => 'DELETE',
'path' => 'servers/{id}/metadata/{key}',
'params' => [
'id' => $this->params->urlId('server'),
'key' => $this->params->key(),
],
];
}
public function getKeypair(): array
{
return [
'method' => 'GET',
'path' => 'os-keypairs/{name}',
'params' => [
'name' => $this->isRequired($this->params->keypairName()),
'userId' => $this->params->userId(),
],
];
}
public function getKeypairs(): array
{
return [
'method' => 'GET',
'path' => 'os-keypairs',
'params' => [
'userId' => $this->params->userId(),
],
];
}
public function postKeypair(): array
{
return [
'method' => 'POST',
'path' => 'os-keypairs',
'jsonKey' => 'keypair',
'params' => [
'name' => $this->isRequired($this->params->name('keypair')),
'publicKey' => $this->params->keypairPublicKey(),
'type' => $this->params->keypairType(),
'userId' => $this->params->keypairUserId(),
],
];
}
public function deleteKeypair(): array
{
return [
'method' => 'DELETE',
'path' => 'os-keypairs/{name}',
'params' => [
'name' => $this->isRequired($this->params->keypairName()),
],
];
}
public function postSecurityGroup(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'jsonKey' => 'addSecurityGroup',
'params' => [
'id' => $this->params->urlId('server'),
'name' => $this->isRequired($this->params->name('securityGroup')),
],
];
}
public function deleteSecurityGroup(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/action',
'jsonKey' => 'removeSecurityGroup',
'params' => [
'id' => $this->params->urlId('server'),
'name' => $this->isRequired($this->params->name('securityGroup')),
],
];
}
public function getSecurityGroups(): array
{
return [
'method' => 'GET',
'path' => 'servers/{id}/os-security-groups',
'jsonKey' => 'security_groups',
'params' => [
'id' => $this->params->urlId('server'),
],
];
}
public function getVolumeAttachments(): array
{
return [
'method' => 'GET',
'path' => 'servers/{id}/os-volume_attachments',
'jsonKey' => 'volumeAttachments',
'params' => [
'id' => $this->params->urlId('server'),
],
];
}
public function postVolumeAttachments(): array
{
return [
'method' => 'POST',
'path' => 'servers/{id}/os-volume_attachments',
'jsonKey' => 'volumeAttachment',
'params' => [
'id' => $this->params->urlId('server'),
'volumeId' => $this->params->volumeId(),
],
];
}
public function deleteVolumeAttachments(): array
{
return [
'method' => 'DELETE',
'path' => 'servers/{id}/os-volume_attachments/{attachmentId}',
'params' => [
'id' => $this->params->urlId('server'),
'attachmentId' => $this->params->attachmentId(),
],
];
}
public function getHypervisorStatistics(): array
{
return [
'method' => 'GET',
'path' => 'os-hypervisors/statistics',
'params' => [
],
];
}
public function getHypervisors(): array
{
return [
'method' => 'GET',
'path' => 'os-hypervisors',
'jsonKey' => 'hypervisors',
'params' => [
'limit' => $this->params->limit(),
'marker' => $this->params->marker(),
],
];
}
public function getHypervisorsDetail(): array
{
$definition = $this->getHypervisors();
$definition['path'] .= '/detail';
return $definition;
}
public function getHypervisor(): array
{
return [
'method' => 'GET',
'path' => 'os-hypervisors/{id}',
'params' => ['id' => $this->params->urlId('id')],
];
}
public function getAvailabilityZones(): array
{
return [
'method' => 'GET',
'path' => 'os-availability-zone/detail',
'params' => [
'limit' => $this->params->limit(),
'marker' => $this->params->marker(),
],
];
}
public function getHosts(): array
{
return [
'method' => 'GET',
'path' => 'os-hosts',
'params' => [
'limit' => $this->params->limit(),
'marker' => $this->params->marker(),
],
];
}
public function getHost(): array
{
return [
'method' => 'GET',
'path' => 'os-hosts/{name}',
'params' => ['name' => $this->params->urlId('name')],
];
}
public function getQuotaSet(): array
{
return [
'method' => 'GET',
'path' => 'os-quota-sets/{tenantId}',
'params' => [
'tenantId' => $this->params->urlId('quota-sets'),
],
];
}
public function getQuotaSetDetail(): array
{
$data = $this->getQuotaSet();
$data['path'] .= '/detail';
return $data;
}
public function deleteQuotaSet(): array
{
return [
'method' => 'DELETE',
'path' => 'os-quota-sets/{tenantId}',
'jsonKey' => 'quota_set',
'params' => [
'tenantId' => $this->params->urlId('quota-sets'),
],
];
}
public function putQuotaSet(): array
{
return [
'method' => 'PUT',
'path' => 'os-quota-sets/{tenantId}',
'jsonKey' => 'quota_set',
'params' => [
'tenantId' => $this->params->idPath(),
'force' => $this->notRequired($this->params->quotaSetLimitForce()),
'instances' => $this->notRequired($this->params->quotaSetLimitInstances()),
'cores' => $this->notRequired($this->params->quotaSetLimitCores()),
'fixedIps' => $this->notRequired($this->params->quotaSetLimitFixedIps()),
'floatingIps' => $this->notRequired($this->params->quotaSetLimitFloatingIps()),
'injectedFileContentBytes' => $this->notRequired($this->params->quotaSetLimitInjectedFileContentBytes()),
'injectedFilePathBytes' => $this->notRequired($this->params->quotaSetLimitInjectedFilePathBytes()),
'injectedFiles' => $this->notRequired($this->params->quotaSetLimitInjectedFiles()),
'keyPairs' => $this->notRequired($this->params->quotaSetLimitKeyPairs()),
'metadataItems' => $this->notRequired($this->params->quotaSetLimitMetadataItems()),
'ram' => $this->notRequired($this->params->quotaSetLimitRam()),
'securityGroupRules' => $this->notRequired($this->params->quotaSetLimitSecurityGroupRules()),
'securityGroups' => $this->notRequired($this->params->quotaSetLimitSecurityGroups()),
'serverGroups' => $this->notRequired($this->params->quotaSetLimitServerGroups()),
'serverGroupMembers' => $this->notRequired($this->params->quotaSetLimitServerGroupMembers()),
],
];
}
}
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2;
/**
* Represents common constants.
*/
abstract class Enum
{
public const REBOOT_SOFT = 'SOFT';
public const REBOOT_HARD = 'HARD';
public const CONSOLE_NOVNC = 'novnc';
public const CONSOLE_XVPNC = 'xvpvnc';
public const CONSOLE_RDP_HTML5 = 'rdp-html5';
public const CONSOLE_SPICE_HTML5 = 'spice-html5';
public const CONSOLE_SERIAL = 'serial';
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2\Models;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
/**
* Represents a Compute v2 AvailabilityZone.
*
* @property \OpenStack\Compute\v2\Api $api
*/
class AvailabilityZone extends OperatorResource implements Listable
{
/** @var string */
public $name;
/** @var string */
public $state;
/** @var array */
public $hosts;
protected $resourceKey = 'availabilityZoneInfo';
protected $resourcesKey = 'availabilityZoneInfo';
protected $aliases = [
'zoneName' => 'name',
'zoneState' => 'state',
];
}
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2\Models;
use OpenStack\Common\Resource\AbstractResource;
/**
* Represents a Compute v2 Fault.
*/
class Fault extends AbstractResource
{
/** @var int * */
public $code;
/** @var \DateTimeImmutable * */
public $created;
/** @var string * */
public $message;
/** @var string */
public $details;
}
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2\Models;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
/**
* Represents a Compute v2 Flavor.
*
* @property \OpenStack\Compute\v2\Api $api
*/
class Flavor extends OperatorResource implements Listable, Retrievable, Creatable, Deletable
{
/** @var int */
public $disk;
/** @var string */
public $id;
/** @var string */
public $name;
/** @var int */
public $ram;
/** @var int */
public $swap;
/** @var int */
public $vcpus;
/** @var array */
public $links;
protected $resourceKey = 'flavor';
protected $resourcesKey = 'flavors';
public function retrieve()
{
$response = $this->execute($this->api->getFlavor(), ['id' => (string) $this->id]);
$this->populateFromResponse($response);
}
public function create(array $userOptions): Creatable
{
$response = $this->execute($this->api->postFlavors(), $userOptions);
return $this->populateFromResponse($response);
}
public function delete()
{
$this->execute($this->api->deleteFlavor(), ['id' => (string) $this->id]);
}
}
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2\Models;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
/**
* Represents a Compute v2 Host.
*
* @property \OpenStack\Compute\v2\Api $api
*/
class Host extends OperatorResource implements Listable, Retrievable
{
/** @var string * */
public $name;
/** @var string * */
public $service;
/** @var string * */
public $zone;
protected $resourceKey = 'host';
protected $resourcesKey = 'hosts';
protected $aliases = [
'host_name' => 'name',
];
public function retrieve()
{
$response = $this->execute($this->api->getHost(), $this->getAttrs(['name']));
$this->populateFromResponse($response);
}
}
@@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2\Models;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
/**
* @property \OpenStack\Compute\v2\Api $api
*/
class Hypervisor extends OperatorResource implements Retrievable, Listable
{
/** @var int */
public $id;
/** @var string */
public $status;
/** @var string */
public $state;
/** @var string */
public $hostIp;
/** @var int */
public $freeDiskGb;
/** @var int */
public $freeRamMb;
/** @var string */
public $hypervisorHostname;
/** @var string */
public $hypervisorType;
/** @var string */
public $hypervisorVersion;
/** @var int */
public $localGb;
/** @var int */
public $localGbUsed;
/** @var int */
public $memoryMb;
/** @var int */
public $memoryMbUsed;
/** @var int */
public $runningVms;
/** @var int */
public $vcpus;
/** @var int */
public $vcpusUsed;
/** @var array */
public $cpuInfo;
/** @var int */
public $currentWorkload;
/** @var int */
public $diskAvailableLeast;
/** @var array */
public $service;
protected $resourceKey = 'hypervisor';
protected $resourcesKey = 'hypervisors';
protected $aliases = [
'host_ip' => 'hostIp',
'free_disk_gb' => 'freeDiskGb',
'free_ram_mb' => 'freeRamMb',
'hypervisor_hostname' => 'hypervisorHostname',
'hypervisor_type' => 'hypervisorType',
'hypervisor_version' => 'hypervisorVersion',
'local_gb' => 'localGb',
'local_gb_used' => 'localGbUsed',
'memory_mb' => 'memoryMb',
'memory_mb_used' => 'memoryMbUsed',
'running_vms' => 'runningVms',
'vcpus_used' => 'vcpusUsed',
'cpu_info' => 'cpuInfo',
'current_workload' => 'currentWorkload',
'disk_available_least' => 'diskAvailableLeast',
];
public function retrieve()
{
$response = $this->execute($this->api->getHypervisor(), ['id' => (string) $this->id]);
$this->populateFromResponse($response);
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2\Models;
use OpenStack\Common\Resource\OperatorResource;
/**
* Represents a Compute v2 Quota.
*
* @property \OpenStack\Compute\v2\Api $api
*/
class HypervisorStatistic extends OperatorResource
{
public $count;
public $vcpus_used;
public $local_gb_used;
public $memory_mb;
public $current_workload;
public $vcpus;
public $running_vms;
public $free_disk_gb;
public $disk_available_least;
public $local_gb;
public $free_ram_mb;
public $memory_mb_used;
protected $resourceKey = 'hypervisor_statistics';
}
@@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2\Models;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\HasMetadata;
use OpenStack\Common\Resource\HasWaiterTrait;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Transport\Utils;
use Psr\Http\Message\ResponseInterface;
/**
* Represents a Compute v2 Image.
*
* @property \OpenStack\Compute\v2\Api $api
*/
class Image extends OperatorResource implements Listable, Retrievable, Deletable, HasMetadata
{
use HasWaiterTrait;
/** @var string */
public $id;
/** @var array */
public $links;
/** @var array */
public $metadata;
/** @var int */
public $minDisk;
/** @var int */
public $minRam;
/** @var string */
public $name;
/** @var string */
public $progress;
/** @var string */
public $status;
/** @var \DateTimeImmutable */
public $created;
/** @var \DateTimeImmutable */
public $updated;
protected $resourceKey = 'image';
protected $resourcesKey = 'images';
protected function getAliases(): array
{
return parent::getAliases() + [
'created' => new Alias('created', \DateTimeImmutable::class),
'updated' => new Alias('updated', \DateTimeImmutable::class),
];
}
public function retrieve()
{
$response = $this->execute($this->api->getImage(), ['id' => (string) $this->id]);
$this->populateFromResponse($response);
}
public function delete()
{
$this->execute($this->api->deleteImage(), ['id' => (string) $this->id]);
}
/**
* Retrieves metadata from the API.
*/
public function getMetadata(): array
{
$response = $this->execute($this->api->getImageMetadata(), ['id' => $this->id]);
return $this->parseMetadata($response);
}
/**
* Resets all the metadata for this image with the values provided. All existing metadata keys
* will either be replaced or removed.
*
* @param array $metadata {@see \OpenStack\Compute\v2\Api::putImageMetadata}
*/
public function resetMetadata(array $metadata)
{
$response = $this->execute($this->api->putImageMetadata(), ['id' => $this->id, 'metadata' => $metadata]);
$this->metadata = $this->parseMetadata($response);
}
/**
* Merges the existing metadata for the image with the values provided. Any existing keys
* referenced in the user options will be replaced with the user's new values. All other
* existing keys will remain unaffected.
*
* @param array $metadata {@see \OpenStack\Compute\v2\Api::postImageMetadata}
*/
public function mergeMetadata(array $metadata)
{
$response = $this->execute($this->api->postImageMetadata(), ['id' => $this->id, 'metadata' => $metadata]);
$this->metadata = $this->parseMetadata($response);
}
/**
* Retrieve the value for a specific metadata key.
*
* @param string $key {@see \OpenStack\Compute\v2\Api::getImageMetadataKey}
*/
public function getMetadataItem(string $key)
{
$response = $this->execute($this->api->getImageMetadataKey(), ['id' => $this->id, 'key' => $key]);
$value = $this->parseMetadata($response)[$key];
$this->metadata[$key] = $value;
return $value;
}
/**
* Remove a specific metadata key.
*
* @param string $key {@see \OpenStack\Compute\v2\Api::deleteImageMetadataKey}
*/
public function deleteMetadataItem(string $key)
{
if (isset($this->metadata[$key])) {
unset($this->metadata[$key]);
}
$this->execute($this->api->deleteImageMetadataKey(), ['id' => $this->id, 'key' => $key]);
}
public function parseMetadata(ResponseInterface $response): array
{
return Utils::jsonDecode($response)['metadata'];
}
}
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2\Models;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Transport\Utils;
/**
* Represents a Compute v2 Keypair.
*
* @property \OpenStack\Compute\v2\Api $api
*/
class Keypair extends OperatorResource implements Listable, Retrievable, Deletable, Creatable
{
/** @var string */
public $fingerprint;
/** @var string */
public $name;
/** @var string */
public $publicKey;
/** @var string */
public $privateKey;
/** @var bool */
public $deleted;
/** @var string */
public $userId;
/** @var string */
public $type;
/**
* @var string
*
* @deprecated Left for backward compatibility only. It is not retrieved from the API.
*/
public $id;
/** @var \DateTimeImmutable */
public $createdAt;
protected $aliases = [
'public_key' => 'publicKey',
'private_key' => 'privateKey',
'user_id' => 'userId',
'type' => 'type',
];
protected $resourceKey = 'keypair';
protected $resourcesKey = 'keypairs';
protected function getAliases(): array
{
return parent::getAliases() + [
'created_at' => new Alias('createdAt', \DateTimeImmutable::class),
];
}
public function retrieve()
{
$response = $this->execute($this->api->getKeypair(), $this->getAttrs(['name', 'userId']));
$this->populateFromResponse($response);
}
public function create(array $userOptions): Creatable
{
$response = $this->execute($this->api->postKeypair(), $userOptions);
return $this->populateFromResponse($response);
}
public function populateFromArray(array $array): self
{
return parent::populateFromArray(Utils::flattenJson($array, $this->resourceKey));
}
public function delete()
{
$this->execute($this->api->deleteKeypair(), ['name' => (string) $this->name]);
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2\Models;
use OpenStack\Common\Resource\AbstractResource;
/**
* Represents a Compute v2 Limit.
*
* @property \OpenStack\Compute\v2\Api $api
*/
class Limit extends AbstractResource
{
/** @var object */
public $rate;
/** @var object */
public $absolute;
protected $resourceKey = 'limits';
}
@@ -0,0 +1,167 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2\Models;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
/**
* Represents a Compute v2 Quota Set.
*
* @property \OpenStack\Compute\v2\Api $api
*/
class QuotaSet extends OperatorResource implements Retrievable, Updateable, Deletable
{
/**
* The number of allowed instance cores for each tenant.
*
* @var int|array
*/
public $cores;
/**
* The number of allowed fixed IP addresses for each tenant.
* Must be equal to or greater than the number of allowed instances.
*
* @deprecated Since Nova v2.35. This attribute will eventually move to Neutron, it is advised you do not use this.
*
* @var int|object
*/
public $fixedIps;
/**
* The number of allowed floating IP addresses for each tenant.
*
* @deprecated Since Nova v2.35. This attribute will eventually move to Neutron, it is advised you do not use this.
*
* @var int|array
*/
public $floatingIps;
/**
* The UUID of the tenant/user the quotas listed for.
*
* @var string
*/
public $tenantId;
/**
* The number of allowed bytes of content for each injected file.
*
* @var int|array
*/
public $injectedFileContentBytes;
/**
* The number of allowed bytes for each injected file path.
*
* @var int|array
*/
public $injectedFilePathBytes;
/**
* The number of allowed injected files for each tenant.
*
* @var int|array
*/
public $injectedFiles;
/**
* The number of allowed instances for each tenant.
*
* @var int|array
*/
public $instances;
/**
* The number of allowed key pairs for each user.
*
* @var int|array
*/
public $keyPairs;
/**
* The number of allowed metadata items for each instance.
*
* @var int|array
*/
public $metadataItems;
/**
* The amount of allowed instance RAM, in MB, for each tenant.
*
* @var int|array
*/
public $ram;
/**
* The number of allowed rules for each security group.
*
* @deprecated Since Nova v2.35. This attribute will eventually move to Neutron, it is advised you do not use this.
*
* @var int|array
*/
public $securityGroupRules;
/**
* The number of allowed security groups for each tenant.
*
* @deprecated Since Nova v2.35. This attribute will eventually move to Neutron, it is advised you do not use this.
*
* @var int|array
*/
public $securityGroups;
/**
* The number of allowed server groups for each tenant.
*
* @var int|array
*/
public $serverGroups;
/**
* The number of allowed members for each server group.
*
* @var int|object
*/
public $serverGroupMembers;
protected $resourceKey = 'quota_set';
protected $aliases = [
'id' => 'tenantId',
'fixed_ips' => 'fixedIps',
'floating_ips' => 'floatingIps',
'injected_file_content_bytes' => 'injectedFileContentBytes',
'injected_file_path_bytes' => 'injectedFilePathBytes',
'injected_files' => 'injectedFiles',
'key_pairs' => 'keyPairs',
'metadata_items' => 'metadataItems',
'security_group_rules' => 'securityGroupRules',
'security_groups' => 'securityGroups',
'server_group_members' => 'serverGroupMembers',
'server_groups' => 'serverGroups',
];
public function retrieve()
{
$response = $this->execute($this->api->getQuotaSet(), ['tenantId' => (string) $this->tenantId]);
$this->populateFromResponse($response);
}
public function delete()
{
$response = $this->executeWithState($this->api->deleteQuotaSet());
$this->populateFromResponse($response);
}
public function update()
{
$response = $this->executeWithState($this->api->putQuotaSet());
$this->populateFromResponse($response);
}
}
@@ -0,0 +1,585 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2\Models;
use OpenStack\BlockStorage\v2\Models\VolumeAttachment;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\HasWaiterTrait;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
use OpenStack\Common\Transport\Utils;
use OpenStack\Compute\v2\Enum;
use OpenStack\Networking\v2\Extensions\SecurityGroups\Models\SecurityGroup;
use OpenStack\Networking\v2\Models\InterfaceAttachment;
use Psr\Http\Message\ResponseInterface;
/**
* @property \OpenStack\Compute\v2\Api $api
*/
class Server extends OperatorResource implements Creatable, Updateable, Deletable, Retrievable, Listable
{
use HasWaiterTrait;
/** @var string */
public $id;
/** @var string */
public $ipv4;
/** @var string */
public $ipv6;
/** @var array */
public $addresses;
/** @var \DateTimeImmutable */
public $created;
/** @var \DateTimeImmutable */
public $updated;
/** @var Flavor */
public $flavor;
/** @var string */
public $hostId;
/** @var string */
public $hypervisorHostname;
/** @var Image */
public $image;
/** @var array */
public $links;
/** @var array */
public $metadata;
/** @var string */
public $name;
/** @var string */
public $progress;
/** @var string */
public $status;
/** @var string */
public $tenantId;
/** @var string */
public $userId;
/** @var string */
public $adminPass;
/** @var string */
public $taskState;
/** @var string */
public $powerState;
/** @var string */
public $vmState;
/** @var Fault */
public $fault;
/** @var string */
public $keyName;
protected $resourceKey = 'server';
protected $resourcesKey = 'servers';
protected $markerKey = 'id';
protected $aliases = [
'block_device_mapping_v2' => 'blockDeviceMapping',
'accessIPv4' => 'ipv4',
'accessIPv6' => 'ipv6',
'tenant_id' => 'tenantId',
'key_name' => 'keyName',
'user_id' => 'userId',
'security_groups' => 'securityGroups',
'OS-EXT-STS:task_state' => 'taskState',
'OS-EXT-STS:power_state' => 'powerState',
'OS-EXT-STS:vm_state' => 'vmState',
'OS-EXT-SRV-ATTR:hypervisor_hostname' => 'hypervisorHostname',
];
protected function getAliases(): array
{
return parent::getAliases() + [
'image' => new Alias('image', Image::class),
'flavor' => new Alias('flavor', Flavor::class),
'created' => new Alias('created', \DateTimeImmutable::class),
'updated' => new Alias('updated', \DateTimeImmutable::class),
];
}
/**
* @param array $userOptions {@see \OpenStack\Compute\v2\Api::postServer}
*/
public function create(array $userOptions): Creatable
{
if (!isset($userOptions['imageId']) && !isset($userOptions['blockDeviceMapping'][0]['uuid'])) {
throw new \RuntimeException('imageId or blockDeviceMapping.uuid must be set.');
}
$response = $this->execute($this->api->postServer(), $userOptions);
return $this->populateFromResponse($response);
}
public function update()
{
$response = $this->execute($this->api->putServer(), $this->getAttrs(['id', 'name', 'ipv4', 'ipv6']));
$this->populateFromResponse($response);
}
public function delete()
{
$this->execute($this->api->deleteServer(), $this->getAttrs(['id']));
}
public function retrieve()
{
$response = $this->execute($this->api->getServer(), $this->getAttrs(['id']));
$this->populateFromResponse($response);
}
/**
* Changes the root password for a server.
*
* @param string $newPassword The new root password
*/
public function changePassword(string $newPassword)
{
$this->execute($this->api->changeServerPassword(), [
'id' => $this->id,
'password' => $newPassword,
]);
}
/**
* Issue a resetState call to the server.
*/
public function resetState()
{
$this->execute($this->api->resetServerState(), [
'id' => $this->id,
'resetState' => ['state' => 'active'],
]);
}
/**
* Reboots the server.
*
* @param string $type The type of reboot that will be performed. Either SOFT or HARD is supported.
*/
public function reboot(string $type = Enum::REBOOT_SOFT)
{
if (!in_array($type, [Enum::REBOOT_SOFT, Enum::REBOOT_HARD])) {
throw new \RuntimeException('Reboot type must either be SOFT or HARD');
}
$this->execute($this->api->rebootServer(), [
'id' => $this->id,
'type' => $type,
]);
}
/**
* Starts server.
*/
public function start()
{
$this->execute($this->api->startServer(), [
'id' => $this->id,
'os-start' => null,
]);
}
/**
* Stops server.
*/
public function stop()
{
$this->execute($this->api->stopServer(), [
'id' => $this->id,
'os-stop' => null,
]);
}
/**
* Resumes server.
*/
public function resume(): void
{
$this->execute($this->api->resumeServer(), [
'id' => $this->id,
'resume' => null,
]);
}
/**
* Suspends server.
*/
public function suspend(): void
{
$this->execute($this->api->suspendServer(), [
'id' => $this->id,
'suspend' => null,
]);
}
/**
* Rebuilds the server.
*
* @param array $options {@see \OpenStack\Compute\v2\Api::rebuildServer}
*/
public function rebuild(array $options)
{
$options['id'] = $this->id;
$response = $this->execute($this->api->rebuildServer(), $options);
$this->populateFromResponse($response);
}
/**
* Rescues the server.
*
* @param array $options {@see \OpenStack\Compute\v2\Api::rescueServer}
*/
public function rescue(array $options): string
{
$options['id'] = $this->id;
$response = $this->execute($this->api->rescueServer(), $options);
return Utils::jsonDecode($response)['adminPass'];
}
/**
* Unrescues the server.
*/
public function unrescue()
{
$this->execute($this->api->unrescueServer(), ['unrescue' => null, 'id' => $this->id]);
}
/**
* Resizes the server to a new flavor. Once this operation is complete and server has transitioned
* to an active state, you will either need to call {@see confirmResize()} or {@see revertResize()}.
*
* @param string $flavorId the UUID of the new flavor your server will be based on
*/
public function resize(string $flavorId)
{
$response = $this->execute($this->api->resizeServer(), [
'id' => $this->id,
'flavorId' => $flavorId,
]);
$this->populateFromResponse($response);
}
/**
* Confirms a previous resize operation.
*/
public function confirmResize()
{
$this->execute($this->api->confirmServerResize(), ['confirmResize' => null, 'id' => $this->id]);
}
/**
* Reverts a previous resize operation.
*/
public function revertResize()
{
$this->execute($this->api->revertServerResize(), ['revertResize' => null, 'id' => $this->id]);
}
/**
* Gets the console output of the server.
*
* @param int $length the number of lines, by default all lines will be returned
*/
public function getConsoleOutput(int $length = -1): string
{
$definition = -1 == $length ? $this->api->getAllConsoleOutput() : $this->api->getConsoleOutput();
$response = $this->execute($definition, [
'os-getConsoleOutput' => new \stdClass(),
'id' => $this->id,
'length' => $length,
]);
return Utils::jsonDecode($response)['output'];
}
/**
* Gets a VNC console for a server.
*
* @param string $type the type of VNC console: novnc|xvpvnc.
* Defaults to novnc
*/
public function getVncConsole($type = Enum::CONSOLE_NOVNC): array
{
$response = $this->execute($this->api->getVncConsole(), ['id' => $this->id, 'type' => $type]);
return Utils::jsonDecode($response)['console'];
}
/**
* Gets a RDP console for a server.
*
* @param string $type the type of VNC console: rdp-html5 (default)
*/
public function getRDPConsole($type = Enum::CONSOLE_RDP_HTML5): array
{
$response = $this->execute($this->api->getRDPConsole(), ['id' => $this->id, 'type' => $type]);
return Utils::jsonDecode($response)['console'];
}
/**
* Gets a Spice console for a server.
*
* @param string $type the type of VNC console: spice-html5
*/
public function getSpiceConsole($type = Enum::CONSOLE_SPICE_HTML5): array
{
$response = $this->execute($this->api->getSpiceConsole(), ['id' => $this->id, 'type' => $type]);
return Utils::jsonDecode($response)['console'];
}
/**
* Gets a serial console for a server.
*
* @param string $type the type of VNC console: serial
*/
public function getSerialConsole($type = Enum::CONSOLE_SERIAL): array
{
$response = $this->execute($this->api->getSerialConsole(), ['id' => $this->id, 'type' => $type]);
return Utils::jsonDecode($response)['console'];
}
/**
* Creates an image for the current server.
*
* @param array $options {@see \OpenStack\Compute\v2\Api::createServerImage}
*/
public function createImage(array $options)
{
$options['id'] = $this->id;
$this->execute($this->api->createServerImage(), $options);
}
/**
* Iterates over all the IP addresses for this server.
*
* @param array $options {@see \OpenStack\Compute\v2\Api::getAddressesByNetwork}
*
* @return array An array containing to two keys: "public" and "private"
*/
public function listAddresses(array $options = []): array
{
$options['id'] = $this->id;
$data = (isset($options['networkLabel'])) ? $this->api->getAddressesByNetwork() : $this->api->getAddresses();
$response = $this->execute($data, $options);
return Utils::jsonDecode($response)['addresses'];
}
/**
* Returns Generator for InterfaceAttachment.
*
* @return \Generator<mixed, \OpenStack\Networking\v2\Models\InterfaceAttachment>
*/
public function listInterfaceAttachments(array $options = []): \Generator
{
return $this->model(InterfaceAttachment::class)->enumerate($this->api->getInterfaceAttachments(), ['id' => $this->id]);
}
/**
* Gets an interface attachment.
*
* @param string $portId the unique ID of the port
*/
public function getInterfaceAttachment(string $portId): InterfaceAttachment
{
$response = $this->execute($this->api->getInterfaceAttachment(), [
'id' => $this->id,
'portId' => $portId,
]);
return $this->model(InterfaceAttachment::class)->populateFromResponse($response);
}
/**
* Creates an interface attachment.
*
* @param array $userOptions {@see \OpenStack\Compute\v2\Api::postInterfaceAttachment}
*/
public function createInterfaceAttachment(array $userOptions): InterfaceAttachment
{
if (!isset($userOptions['networkId']) && !isset($userOptions['portId'])) {
throw new \RuntimeException('networkId or portId must be set.');
}
$response = $this->execute($this->api->postInterfaceAttachment(), array_merge($userOptions, ['id' => $this->id]));
return $this->model(InterfaceAttachment::class)->populateFromResponse($response);
}
/**
* Detaches an interface attachment.
*/
public function detachInterface(string $portId)
{
$this->execute($this->api->deleteInterfaceAttachment(), [
'id' => $this->id,
'portId' => $portId,
]);
}
/**
* Retrieves metadata from the API.
*/
public function getMetadata(): array
{
$response = $this->execute($this->api->getServerMetadata(), ['id' => $this->id]);
return $this->parseMetadata($response);
}
/**
* Resets all the metadata for this server with the values provided. All existing metadata keys
* will either be replaced or removed.
*
* @param array $metadata {@see \OpenStack\Compute\v2\Api::putServerMetadata}
*/
public function resetMetadata(array $metadata)
{
$response = $this->execute($this->api->putServerMetadata(), ['id' => $this->id, 'metadata' => $metadata]);
$this->metadata = $this->parseMetadata($response);
}
/**
* Merges the existing metadata for the server with the values provided. Any existing keys
* referenced in the user options will be replaced with the user's new values. All other
* existing keys will remain unaffected.
*
* @param array $metadata {@see \OpenStack\Compute\v2\Api::postServerMetadata}
*/
public function mergeMetadata(array $metadata)
{
$response = $this->execute($this->api->postServerMetadata(), ['id' => $this->id, 'metadata' => $metadata]);
$this->metadata = $this->parseMetadata($response);
}
/**
* Retrieve the value for a specific metadata key.
*
* @param string $key {@see \OpenStack\Compute\v2\Api::getServerMetadataKey}
*/
public function getMetadataItem(string $key)
{
$response = $this->execute($this->api->getServerMetadataKey(), ['id' => $this->id, 'key' => $key]);
$value = $this->parseMetadata($response)[$key];
$this->metadata[$key] = $value;
return $value;
}
/**
* Remove a specific metadata key.
*
* @param string $key {@see \OpenStack\Compute\v2\Api::deleteServerMetadataKey}
*/
public function deleteMetadataItem(string $key)
{
if (isset($this->metadata[$key])) {
unset($this->metadata[$key]);
}
$this->execute($this->api->deleteServerMetadataKey(), ['id' => $this->id, 'key' => $key]);
}
/**
* Add security group to a server (addSecurityGroup action).
*
* @param array $options {@see \OpenStack\Compute\v2\Api::postSecurityGroup}
*/
public function addSecurityGroup(array $options): SecurityGroup
{
$options['id'] = $this->id;
$response = $this->execute($this->api->postSecurityGroup(), $options);
return $this->model(SecurityGroup::class)->populateFromResponse($response);
}
/**
* Add security group to a server (addSecurityGroup action).
*
* @param array $options {@see \OpenStack\Compute\v2\Api::deleteSecurityGroup}
*/
public function removeSecurityGroup(array $options)
{
$options['id'] = $this->id;
$this->execute($this->api->deleteSecurityGroup(), $options);
}
public function parseMetadata(ResponseInterface $response): array
{
return Utils::jsonDecode($response)['metadata'];
}
/**
* Returns Generator for SecurityGroups.
*
* @return \Generator<mixed, \OpenStack\Networking\v2\Extensions\SecurityGroups\Models\SecurityGroup>
*/
public function listSecurityGroups(): \Generator
{
return $this->model(SecurityGroup::class)->enumerate($this->api->getSecurityGroups(), ['id' => $this->id]);
}
/**
* Returns Generator for VolumeAttachment.
*
* @return \Generator<mixed, \OpenStack\BlockStorage\v2\Models\VolumeAttachment>
*/
public function listVolumeAttachments(): \Generator
{
return $this->model(VolumeAttachment::class)->enumerate($this->api->getVolumeAttachments(), ['id' => $this->id]);
}
/**
* Attach a volume and returns volume that was attached.
*/
public function attachVolume(string $volumeId): VolumeAttachment
{
$response = $this->execute($this->api->postVolumeAttachments(), ['id' => $this->id, 'volumeId' => $volumeId]);
return $this->model(VolumeAttachment::class)->populateFromResponse($response);
}
/**
* Detach a volume.
*/
public function detachVolume(string $attachmentId)
{
$this->execute($this->api->deleteVolumeAttachments(), ['id' => $this->id, 'attachmentId' => $attachmentId]);
}
}
@@ -0,0 +1,665 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2;
use OpenStack\Common\Api\AbstractParams;
class Params extends AbstractParams
{
public function urlId(string $type): array
{
return array_merge(parent::id($type), [
'required' => true,
'location' => self::URL,
'documented' => false,
]);
}
public function resetState(): array
{
return [
'type' => self::OBJECT_TYPE,
'location' => self::JSON,
'sentAs' => 'os-resetState',
'required' => true,
'properties' => [
'state' => ['type' => self::STRING_TYPE],
],
];
}
public function minDisk(): array
{
return [
'type' => self::INT_TYPE,
'location' => self::QUERY,
'description' => 'Return flavors that have a minimum disk space in GB.',
];
}
public function minRam(): array
{
return [
'type' => self::INT_TYPE,
'location' => self::QUERY,
'description' => 'Return flavors that have a minimum RAM size in GB.',
];
}
public function flavorName(): array
{
return [
'location' => self::QUERY,
'description' => 'Return images which match a certain name.',
];
}
public function filterChangesSince($type)
{
return [
'location' => self::QUERY,
'sentAs' => 'changes-since',
'description' => sprintf(
'Return %ss which have been changed since a certain time. This value needs to be in an ISO 8601 format.',
$type
),
];
}
public function flavorServer(): array
{
return [
'location' => self::QUERY,
'description' => sprintf('Return images which are associated with a server. This value needs to be in a URL format.'),
];
}
public function filterStatus(string $type): array
{
return [
'location' => self::QUERY,
'description' => sprintf(
'Return %ss that have a particular status, such as "ACTIVE".',
$type
),
];
}
public function flavorType(): array
{
return [
'location' => self::QUERY,
'description' => 'Return images that are of a particular type, such as "snapshot" or "backup".',
];
}
public function key(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::URL,
'required' => true,
'description' => 'The specific metadata key you are interacting with',
];
}
public function ipv4(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'sentAs' => 'accessIPv4',
'description' => 'The IP address (version 4) of the remote resource',
];
}
public function ipv6(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'sentAs' => 'accessIPv6',
'description' => 'The IP address (version 6) of the remote resource',
];
}
public function imageId(): array
{
return [
'type' => self::STRING_TYPE,
'required' => true,
'sentAs' => 'imageRef',
'description' => 'The UUID of the image to use for your server instance. This is not required in case of boot from volume. In all other cases it is required and must be a valid UUID',
];
}
public function rescueImageId(): array
{
return [
'type' => self::STRING_TYPE,
'required' => true,
'sentAs' => 'rescue_image_ref',
'description' => 'The image reference to use to rescue your server instance. Specify the image reference by ID or full URL. If you omit an image reference, default is the base image reference',
];
}
public function flavorId(): array
{
return [
'type' => self::STRING_TYPE,
'required' => true,
'sentAs' => 'flavorRef',
'description' => 'The unique ID of the flavor that this server will be based on',
];
}
public function networkId(): array
{
return [
'type' => self::STRING_TYPE,
'required' => true,
'sentAs' => 'net_id',
'description' => 'The unique ID of a network',
];
}
public function portId(): array
{
return [
'type' => self::STRING_TYPE,
'required' => true,
'sentAs' => 'port_id',
'description' => 'The unique ID of a port',
];
}
public function tag(): array
{
return [
'type' => self::STRING_TYPE,
];
}
public function fixedIpAddresses(): array
{
return [
'type' => self::ARRAY_TYPE,
'sentAs' => 'fixed_ips',
'description' => 'A list of ip addresses which this interface will be associated with',
'items' => [
'type' => self::OBJECT_TYPE,
'properties' => ['ip_address' => ['type' => self::STRING_TYPE]],
],
];
}
public function metadata(): array
{
return [
'type' => self::OBJECT_TYPE,
'location' => self::JSON,
'required' => true,
'description' => 'An arbitrary key/value pairing that will be used for metadata.',
'properties' => [
'type' => self::STRING_TYPE,
'description' => <<<TYPEOTHER
The value being set for your key. Bear in mind that "key" is just an example, you can name it anything.
TYPEOTHER
],
];
}
public function personality(): array
{
return [
'type' => self::ARRAY_TYPE,
'items' => [
'type' => self::OBJECT_TYPE,
'properties' => [
'path' => [
'type' => self::STRING_TYPE,
'description' => 'The path, on the filesystem, where the personality file will be placed',
],
'contents' => [
'type' => self::STRING_TYPE,
'description' => 'Base64-encoded content of the personality file',
],
],
],
'description' => <<<EOL
File path and contents (text only) to inject into the server at launch. The maximum size of the file path data is 255
bytes. The maximum limit refers to the number of bytes in the decoded data and not the number of characters in the
encoded data.
EOL
];
}
public function securityGroups(): array
{
return [
'type' => self::ARRAY_TYPE,
'sentAs' => 'security_groups',
'description' => 'A list of security group objects which this server will be associated with',
'items' => [
'type' => self::OBJECT_TYPE,
'properties' => ['name' => $this->name('security group')],
],
];
}
public function userData(): array
{
return [
'type' => self::STRING_TYPE,
'sentAs' => 'user_data',
'description' => 'Configuration information or scripts to use upon launch. Must be Base64 encoded.',
];
}
public function availabilityZone(): array
{
return [
'type' => self::STRING_TYPE,
'sentAs' => 'availability_zone',
'description' => 'The availability zone in which to launch the server.',
];
}
public function networks(): array
{
return [
'type' => self::ARRAY_TYPE,
'description' => <<<EOT
A list of network objects which this server will be associated with. By default, the server instance is provisioned
with all isolated networks for the tenant. Optionally, you can create one or more NICs on the server.
To provision the server instance with a NIC for a network, specify the UUID of the network in the uuid attribute in a
networks object.
To provision the server instance with a NIC for an already existing port, specify the port-id in the port attribute in
a networks object.
EOT
,
'items' => [
'type' => self::OBJECT_TYPE,
'properties' => [
'uuid' => [
'type' => self::STRING_TYPE,
'description' => <<<EOL
To provision the server instance with a NIC for a network, specify the UUID of the network in the uuid attribute in a
networks object. Required if you omit the port attribute
EOL
],
'port' => [
'type' => self::STRING_TYPE,
'description' => <<<EOL
To provision the server instance with a NIC for an already existing port, specify the port-id in the port attribute in
a networks object. The port status must be DOWN. Required if you omit the uuid attribute.
EOL
],
],
],
];
}
public function blockDeviceMapping(): array
{
return [
'type' => self::ARRAY_TYPE,
'sentAs' => 'block_device_mapping_v2',
'description' => <<<EOL
Enables booting the server from a volume when additional parameters are given. If specified, the volume status must be
available, and the volume attach_status in OpenStack Block Storage DB must be detached.
EOL
,
'items' => [
'type' => self::OBJECT_TYPE,
'properties' => [
'uuid' => [
'type' => self::STRING_TYPE,
'description' => 'The unique ID for the volume which the server is to be booted from.',
],
'bootIndex' => [
'type' => self::INT_TYPE,
'sentAs' => 'boot_index',
'description' => 'Indicates a number designating the boot order of the device. Use -1 for the boot volume, choose 0 for an attached volume.',
],
'deleteOnTermination' => [
'type' => self::BOOL_TYPE,
'sentAs' => 'delete_on_termination',
'description' => 'To delete the boot volume when the server stops, specify true. Otherwise, specify false.',
],
'guestFormat' => [
'type' => self::STRING_TYPE,
'sentAs' => 'guest_format',
'description' => 'Specifies the guest server disk file system format, such as "ephemeral" or "swap".',
],
'destinationType' => [
'type' => self::STRING_TYPE,
'sentAs' => 'destination_type',
'description' => 'Describes where the volume comes from. Choices are "local" or "volume". When using "volume" the volume ID',
],
'sourceType' => [
'type' => self::STRING_TYPE,
'sentAs' => 'source_type',
'description' => 'Describes the volume source type for the volume. Choices are "blank", "snapshot", "volume", or "image".',
],
'deviceName' => [
'type' => self::STRING_TYPE,
'sentAs' => 'device_name',
'description' => 'Describes a path to the device for the volume you want to use to boot the server.',
],
'volumeSize' => [
'type' => self::INT_TYPE,
'sentAs' => 'volume_size',
'description' => 'Size of the volume created if we are doing vol creation',
],
'volumeType' => [
'type' => self::STRING_TYPE,
'sentAs' => 'volume_type',
'description' => 'The type of volume which the compute service will create and attach to the server.',
],
],
],
];
}
public function filterHost(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::QUERY,
'description' => '',
];
}
public function filterName(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::QUERY,
'description' => '',
];
}
public function filterFlavor(): array
{
return [
'sentAs' => 'flavor',
'type' => self::STRING_TYPE,
'location' => self::QUERY,
'description' => '',
];
}
public function filterImage(): array
{
return [
'sentAs' => 'image',
'type' => self::STRING_TYPE,
'location' => self::QUERY,
'description' => '',
];
}
public function password(): array
{
return [
'sentAs' => 'adminPass',
'type' => self::STRING_TYPE,
'location' => self::JSON,
'required' => true,
'description' => '',
];
}
public function rebootType(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'required' => true,
'description' => '',
];
}
public function nullAction(): array
{
return [
'type' => self::NULL_TYPE,
'location' => self::JSON,
'required' => true,
];
}
public function networkLabel(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::URL,
'required' => true,
];
}
public function keyName(): array
{
return [
'type' => self::STRING_TYPE,
'required' => false,
'sentAs' => 'key_name',
'description' => 'The key name',
];
}
public function keypairPublicKey(): array
{
return [
'type' => self::STRING_TYPE,
'sentAs' => 'public_key',
'location' => self::JSON,
'description' => 'The public ssh key to import. If you omit this value, a key is generated.',
];
}
public function keypairName(): array
{
return [
'location' => self::URL,
];
}
public function userId(): array
{
return [
'type' => self::STRING_TYPE,
'sentAs' => 'user_id',
'location' => self::QUERY,
'description' => 'This allows administrative users to operate key-pairs of specified user ID. Requires micro version 2.10.',
];
}
public function keypairUserId(): array
{
return [
'type' => self::STRING_TYPE,
'sentAs' => 'user_id',
'location' => self::JSON,
'description' => 'This allows administrative users to upload keys for other users than themselves. Requires micro version 2.10.',
];
}
public function keypairType(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'description' => 'The type of the keypair. Allowed values are ssh or x509. Require micro version 2.2.',
];
}
public function flavorRam(): array
{
return [
'type' => self::INT_TYPE,
'location' => self::JSON,
];
}
public function flavorVcpus(): array
{
return [
'type' => self::INT_TYPE,
'location' => self::JSON,
];
}
public function flavorDisk(): array
{
return [
'type' => self::INT_TYPE,
'location' => self::JSON,
];
}
public function flavorSwap(): array
{
return [
'type' => self::INT_TYPE,
'location' => self::JSON,
];
}
public function volumeId(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
];
}
public function attachmentId(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::URL,
'required' => true,
];
}
public function consoleType(): array
{
return [
'type' => self::STRING_TYPE,
'location' => self::JSON,
'required' => true,
];
}
public function consoleLogLength(): array
{
return [
'type' => self::INT_TYPE,
'location' => self::JSON,
'required' => false,
];
}
public function emptyObject(): array
{
return [
'type' => self::OBJECT_TYPE,
];
}
protected function quotaSetLimit($sentAs, $description): array
{
return [
'type' => self::INT_TYPE,
'location' => self::JSON,
'sentAs' => $sentAs,
'description' => $description,
];
}
public function quotaSetLimitForce(): array
{
return [
'type' => self::BOOLEAN_TYPE,
'location' => self::JSON,
'sentAs' => 'force',
'description' => 'You can force the update even if the quota has already been used and the reserved quota exceeds the new quota',
];
}
public function quotaSetLimitInstances(): array
{
return $this->quotaSetLimit('instances', 'The number of allowed instances for each tenant.');
}
public function quotaSetLimitCores(): array
{
return $this->quotaSetLimit('cores', 'The number of allowed instance cores for each tenant.');
}
public function quotaSetLimitFixedIps(): array
{
return $this->quotaSetLimit('fixed_ips', 'The number of allowed fixed IP addresses for each tenant. Must be equal to or greater than the number of allowed instances.');
}
public function quotaSetLimitFloatingIps(): array
{
return $this->quotaSetLimit('floating_ips', 'The number of allowed floating IP addresses for each tenant.');
}
public function quotaSetLimitInjectedFileContentBytes(): array
{
return $this->quotaSetLimit('injected_file_content_bytes', 'The number of allowed bytes of content for each injected file.');
}
public function quotaSetLimitInjectedFilePathBytes(): array
{
return $this->quotaSetLimit('injected_file_path_bytes', 'The number of allowed bytes for each injected file path.');
}
public function quotaSetLimitInjectedFiles(): array
{
return $this->quotaSetLimit('injected_files', 'The number of allowed injected files for each tenant.');
}
public function quotaSetLimitKeyPairs(): array
{
return $this->quotaSetLimit('key_pairs', 'The number of allowed key pairs for each user.');
}
public function quotaSetLimitMetadataItems(): array
{
return $this->quotaSetLimit('metadata_items', 'The number of allowed metadata items for each instance.');
}
public function quotaSetLimitRam(): array
{
return $this->quotaSetLimit('ram', 'The amount of allowed instance RAM (in MB) for each tenant.');
}
public function quotaSetLimitSecurityGroupRules(): array
{
return $this->quotaSetLimit('security_group_rules', 'The number of allowed rules for each security group.');
}
public function quotaSetLimitSecurityGroups(): array
{
return $this->quotaSetLimit('security_groups', 'The number of allowed security groups for each tenant.');
}
public function quotaSetLimitServerGroups(): array
{
return $this->quotaSetLimit('server_groups', 'The number of allowed server groups for each tenant.');
}
public function quotaSetLimitServerGroupMembers(): array
{
return $this->quotaSetLimit('server_group_members', 'The number of allowed members for each server group.');
}
}
@@ -0,0 +1,278 @@
<?php
declare(strict_types=1);
namespace OpenStack\Compute\v2;
use OpenStack\Common\Service\AbstractService;
use OpenStack\Compute\v2\Models\AvailabilityZone;
use OpenStack\Compute\v2\Models\Flavor;
use OpenStack\Compute\v2\Models\Host;
use OpenStack\Compute\v2\Models\Hypervisor;
use OpenStack\Compute\v2\Models\HypervisorStatistic;
use OpenStack\Compute\v2\Models\Image;
use OpenStack\Compute\v2\Models\Keypair;
use OpenStack\Compute\v2\Models\Limit;
use OpenStack\Compute\v2\Models\QuotaSet;
use OpenStack\Compute\v2\Models\Server;
/**
* Compute v2 service for OpenStack.
*
* @property Api $api
*/
class Service extends AbstractService
{
/**
* Create a new server resource. This operation will provision a new virtual machine on a host chosen by your
* service API.
*
* @param array $options {@see \OpenStack\Compute\v2\Api::postServer}
*/
public function createServer(array $options): Server
{
return $this->model(Server::class)->create($options);
}
/**
* List servers.
*
* @param bool $detailed Determines whether detailed information will be returned. If FALSE is specified, only
* the ID, name and links attributes are returned, saving bandwidth.
* @param array $options {@see \OpenStack\Compute\v2\Api::getServers}
* @param callable|null $mapFn a callable function that will be invoked on every iteration of the list
*
* @return \Generator<mixed, \OpenStack\Compute\v2\Models\Server>
*/
public function listServers(bool $detailed = false, array $options = [], ?callable $mapFn = null): \Generator
{
$def = (true === $detailed) ? $this->api->getServersDetail() : $this->api->getServers();
return $this->model(Server::class)->enumerate($def, $options, $mapFn);
}
/**
* Retrieve a server object without calling the remote API. Any values provided in the array will populate the
* empty object, allowing you greater control without the expense of network transactions. To call the remote API
* and have the response populate the object, call {@see Server::retrieve}. For example:.
*
* <code>$server = $service->getServer(['id' => '{serverId}']);</code>
*
* @param array $options An array of attributes that will be set on the {@see Server} object. The array keys need to
* correspond to the class public properties.
*/
public function getServer(array $options = []): Server
{
$server = $this->model(Server::class);
$server->populateFromArray($options);
return $server;
}
/**
* List flavors.
*
* @param array $options {@see \OpenStack\Compute\v2\Api::getFlavors}
* @param callable|null $mapFn a callable function that will be invoked on every iteration of the list
* @param bool $detailed set to true to fetch flavors' details
*
* @return \Generator<mixed, \OpenStack\Compute\v2\Models\Flavor>
*/
public function listFlavors(array $options = [], ?callable $mapFn = null, bool $detailed = false): \Generator
{
$def = true === $detailed ? $this->api->getFlavorsDetail() : $this->api->getFlavors();
return $this->model(Flavor::class)->enumerate($def, $options, $mapFn);
}
/**
* Retrieve a flavor object without calling the remote API. Any values provided in the array will populate the
* empty object, allowing you greater control without the expense of network transactions. To call the remote API
* and have the response populate the object, call {@see Flavor::retrieve}.
*
* @param array $options An array of attributes that will be set on the {@see Flavor} object. The array keys need to
* correspond to the class public properties.
*/
public function getFlavor(array $options = []): Flavor
{
$flavor = $this->model(Flavor::class);
$flavor->populateFromArray($options);
return $flavor;
}
/**
* Create a new flavor resource.
*
* @param array $options {@see \OpenStack\Compute\v2\Api::postFlavors}
*/
public function createFlavor(array $options = []): Flavor
{
return $this->model(Flavor::class)->create($options);
}
/**
* List images.
*
* @param array $options {@see \OpenStack\Compute\v2\Api::getImages}
* @param callable|null $mapFn a callable function that will be invoked on every iteration of the list
*
* @return \Generator<mixed, \OpenStack\Compute\v2\Models\Image>
*/
public function listImages(array $options = [], ?callable $mapFn = null): \Generator
{
return $this->model(Image::class)->enumerate($this->api->getImages(), $options, $mapFn);
}
/**
* Retrieve an image object without calling the remote API. Any values provided in the array will populate the
* empty object, allowing you greater control without the expense of network transactions. To call the remote API
* and have the response populate the object, call {@see Image::retrieve}.
*
* @param array $options An array of attributes that will be set on the {@see Image} object. The array keys need to
* correspond to the class public properties.
*/
public function getImage(array $options = []): Image
{
$image = $this->model(Image::class);
$image->populateFromArray($options);
return $image;
}
/**
* List key pairs.
*
* @param array $options {@see \OpenStack\Compute\v2\Api::getKeyPairs}
* @param callable|null $mapFn a callable function that will be invoked on every iteration of the list
*
* @return \Generator<mixed, \OpenStack\Compute\v2\Models\Keypair>
*/
public function listKeypairs(array $options = [], ?callable $mapFn = null): \Generator
{
return $this->model(Keypair::class)->enumerate($this->api->getKeypairs(), $options, $mapFn);
}
/**
* Create or import keypair.
*/
public function createKeypair(array $options): Keypair
{
return $this->model(Keypair::class)->create($options);
}
/**
* Get keypair.
*/
public function getKeypair(array $options = []): Keypair
{
$keypair = $this->model(Keypair::class);
$keypair->populateFromArray($options);
return $keypair;
}
/**
* Shows rate and absolute limits for the tenant.
*/
public function getLimits(): Limit
{
$limits = $this->model(Limit::class);
$limits->populateFromResponse($this->execute($this->api->getLimits(), []));
return $limits;
}
/**
* Shows summary statistics for all hypervisors over all compute nodes.
*/
public function getHypervisorStatistics(): HypervisorStatistic
{
$statistics = $this->model(HypervisorStatistic::class);
$statistics->populateFromResponse($this->execute($this->api->getHypervisorStatistics(), []));
return $statistics;
}
/**
* List hypervisors.
*
* @param bool $detailed Determines whether detailed information will be returned. If FALSE is specified, only
* the ID, name and links attributes are returned, saving bandwidth.
* @param array $options {@see \OpenStack\Compute\v2\Api::getHypervisors}
* @param callable|null $mapFn a callable function that will be invoked on every iteration of the list
*
* @return \Generator<mixed, \OpenStack\Compute\v2\Models\Hypervisor>
*/
public function listHypervisors(bool $detailed = false, array $options = [], ?callable $mapFn = null): \Generator
{
$def = (true === $detailed) ? $this->api->getHypervisorsDetail() : $this->api->getHypervisors();
return $this->model(Hypervisor::class)->enumerate($def, $options, $mapFn);
}
/**
* Shows details for a given hypervisor.
*/
public function getHypervisor(array $options = []): Hypervisor
{
$hypervisor = $this->model(Hypervisor::class);
return $hypervisor->populateFromArray($options);
}
/**
* List hosts.
*
* @param array $options {@see \OpenStack\Compute\v2\Api::getHosts}
* @param callable|null $mapFn a callable function that will be invoked on every iteration of the list
*
* @return \Generator<mixed, \OpenStack\Compute\v2\Models\Host>
*/
public function listHosts(array $options = [], ?callable $mapFn = null): \Generator
{
return $this->model(Host::class)->enumerate($this->api->getHosts(), $options, $mapFn);
}
/**
* Retrieve a host object without calling the remote API. Any values provided in the array will populate the
* empty object, allowing you greater control without the expense of network transactions. To call the remote API
* and have the response populate the object, call {@see Host::retrieve}. For example:.
*
* <code>$server = $service->getHost(['name' => '{name}']);</code>
*
* @param array $options An array of attributes that will be set on the {@see Host} object. The array keys need to
* correspond to the class public properties.
*/
public function getHost(array $options = []): Host
{
$host = $this->model(Host::class);
$host->populateFromArray($options);
return $host;
}
/**
* List AZs.
*
* @param array $options {@see \OpenStack\Compute\v2\Api::getAvailabilityZones}
* @param callable|null $mapFn a callable function that will be invoked on every iteration of the list
*
* @return \Generator<mixed, \OpenStack\Compute\v2\Models\AvailabilityZone>
*/
public function listAvailabilityZones(array $options = [], ?callable $mapFn = null): \Generator
{
return $this->model(AvailabilityZone::class)->enumerate($this->api->getAvailabilityZones(), $options, $mapFn);
}
/**
* Shows A Quota for a tenant.
*/
public function getQuotaSet(string $tenantId, bool $detailed = false): QuotaSet
{
$quotaSet = $this->model(QuotaSet::class);
$quotaSet->populateFromResponse($this->execute($detailed ? $this->api->getQuotaSetDetail() : $this->api->getQuotaSet(), ['tenantId' => $tenantId]));
return $quotaSet;
}
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v2;
use OpenStack\Common\Api\ApiInterface;
/**
* Represents the OpenStack Identity v2 API.
*/
class Api implements ApiInterface
{
public function postToken(): array
{
return [
'method' => 'POST',
'path' => 'tokens',
'skipAuth' => true,
'params' => [
'username' => [
'type' => 'string',
'required' => true,
'path' => 'auth.passwordCredentials',
],
'password' => [
'type' => 'string',
'required' => true,
'path' => 'auth.passwordCredentials',
],
'tenantId' => [
'type' => 'string',
'path' => 'auth',
],
'tenantName' => [
'type' => 'string',
'path' => 'auth',
],
],
];
}
}
@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v2\Models;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Transport\Utils;
use Psr\Http\Message\ResponseInterface;
/**
* Represents an Identity v2 service catalog.
*/
class Catalog extends OperatorResource implements \OpenStack\Common\Auth\Catalog
{
public const DEFAULT_URL_TYPE = 'publicURL';
/**
* The catalog entries.
*
* @var []Entry
*/
public $entries = [];
protected function getAliases(): array
{
return parent::getAliases() + [
'entries' => new Alias('entries', Entry::class, true),
];
}
public function populateFromResponse(ResponseInterface $response): self
{
$entries = Utils::jsonDecode($response)['access']['serviceCatalog'];
foreach ($entries as $entry) {
$this->entries[] = $this->model(Entry::class, $entry);
}
return $this;
}
public function getServiceUrl(
string $serviceName,
string $serviceType,
string $region,
string $urlType = self::DEFAULT_URL_TYPE
): string {
foreach ($this->entries as $entry) {
if ($entry->matches($serviceName, $serviceType) && ($url = $entry->getEndpointUrl($region, $urlType))) {
return $url;
}
}
throw new \RuntimeException(sprintf("Endpoint URL could not be found in the catalog for this service.\nName: %s\nType: %s\nRegion: %s\nURL type: %s", $serviceName, $serviceType, $region, $urlType));
}
}
@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v2\Models;
use OpenStack\Common\HydratorStrategyTrait;
use OpenStack\Common\Resource\OperatorResource;
/**
* Represents an Identity v2 catalog entry endpoint.
*/
class Endpoint extends OperatorResource
{
use HydratorStrategyTrait;
/** @var string */
public $adminUrl;
/** @var string */
public $region;
/** @var string */
public $internalUrl;
/** @var string */
public $publicUrl;
protected $aliases = [
'adminURL' => 'adminUrl',
'internalURL' => 'internalUrl',
'publicURL' => 'publicUrl',
];
/**
* Indicates whether a given region is supported.
*/
public function supportsRegion(string $region): bool
{
return $this->region == $region;
}
/**
* Indicates whether a given URL type is supported.
*/
public function supportsUrlType(string $urlType): bool
{
$supported = false;
switch (strtolower($urlType)) {
case 'internalurl':
case 'publicurl':
case 'adminurl':
$supported = true;
break;
}
return $supported;
}
/**
* Retrieves a URL for the endpoint based on a specific type.
*
* @param string $urlType Either "internalURL", "publicURL" or "adminURL" (case insensitive)
*
* @return bool|string
*/
public function getUrl(string $urlType): string
{
$url = false;
switch (strtolower($urlType)) {
case 'internalurl':
$url = $this->internalUrl;
break;
case 'publicurl':
$url = $this->publicUrl;
break;
case 'adminurl':
$url = $this->adminUrl;
break;
}
return $url;
}
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v2\Models;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\OperatorResource;
/**
* Represents an Identity v2 Catalog Entry.
*/
class Entry extends OperatorResource
{
/** @var string */
public $name;
/** @var string */
public $type;
/** @var []Endpoint */
public $endpoints = [];
protected function getAliases(): array
{
return parent::getAliases() + [
'endpoints' => new Alias('endpoints', Endpoint::class, true),
];
}
/**
* Indicates whether this catalog entry matches a certain name and type.
*
* @return bool TRUE if it's a match, FALSE if not
*/
public function matches(string $name, string $type): bool
{
return $this->name == $name && $this->type == $type;
}
/**
* Retrieves the catalog entry's URL according to a specific region and URL type.
*/
public function getEndpointUrl(string $region, string $urlType): string
{
foreach ($this->endpoints as $endpoint) {
if ($endpoint->supportsRegion($region) && $endpoint->supportsUrlType($urlType)) {
return $endpoint->getUrl($urlType);
}
}
return '';
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v2\Models;
use OpenStack\Common\Resource\OperatorResource;
/**
* Represents an Identity v2 Tenant.
*/
class Tenant extends OperatorResource
{
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v2\Models;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Transport\Utils;
use Psr\Http\Message\ResponseInterface;
/**
* Represents an Identity v2 Token.
*/
class Token extends OperatorResource implements \OpenStack\Common\Auth\Token
{
/** @var \DateTimeImmutable */
public $issuedAt;
/** @var string */
public $id;
/** @var \DateTimeImmutable */
public $expires;
/** @var Tenant */
public $tenant;
protected function getAliases(): array
{
return parent::getAliases() + [
'tenant' => new Alias('tenant', Tenant::class),
'expires' => new Alias('expires', \DateTimeImmutable::class),
'issued_at' => new Alias('issuedAt', \DateTimeImmutable::class),
];
}
public function populateFromResponse(ResponseInterface $response): self
{
$this->populateFromArray(Utils::jsonDecode($response)['access']['token']);
return $this;
}
public function getId(): string
{
return $this->id;
}
public function hasExpired(): bool
{
return $this->expires <= new \DateTimeImmutable('now', $this->expires->getTimezone());
}
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace OpenStack\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 OpenStack 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->postToken();
$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}
*/
public function generateToken(array $options = []): Token
{
$response = $this->execute($this->api->postToken(), $options);
return $this->model(Token::class, $response);
}
}
+882
View File
@@ -0,0 +1,882 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3;
use OpenStack\Common\Api\AbstractApi;
class Api extends AbstractApi
{
public function __construct()
{
$this->params = new Params();
}
public function postTokens(): array
{
return [
'method' => 'POST',
'path' => 'auth/tokens',
'skipAuth' => true,
'params' => [
'methods' => $this->params->methods(),
'user' => $this->params->user(),
'application_credential' => $this->params->applicationCredential(),
'tokenId' => $this->params->tokenBody(),
'scope' => $this->params->scope(),
],
];
}
public function getTokens(): array
{
return [
'method' => 'GET',
'path' => 'auth/tokens',
'params' => ['tokenId' => $this->params->tokenId()],
];
}
public function headTokens(): array
{
return [
'method' => 'HEAD',
'path' => 'auth/tokens',
'params' => ['tokenId' => $this->params->tokenId()],
];
}
public function deleteTokens(): array
{
return [
'method' => 'DELETE',
'path' => 'auth/tokens',
'params' => ['tokenId' => $this->params->tokenId()],
];
}
public function postServices(): array
{
return [
'method' => 'POST',
'path' => 'services',
'jsonKey' => 'service',
'params' => [
'name' => $this->params->name('service'),
'type' => $this->params->type('service'),
'description' => $this->params->desc('service'),
],
];
}
public function getServices(): array
{
return [
'method' => 'GET',
'path' => 'services',
'params' => ['type' => $this->params->typeQuery()],
];
}
public function getService(): array
{
return [
'method' => 'GET',
'path' => 'services/{id}',
'params' => ['id' => $this->params->idUrl('service')],
];
}
public function patchService(): array
{
return [
'method' => 'PATCH',
'path' => 'services/{id}',
'jsonKey' => 'service',
'params' => [
'id' => $this->params->idUrl('service'),
'name' => $this->params->name('service'),
'type' => $this->params->type('service'),
'description' => $this->params->desc('service'),
],
];
}
public function deleteService(): array
{
return [
'method' => 'DELETE',
'path' => 'services/{id}',
'params' => ['id' => $this->params->idUrl('service')],
];
}
public function postEndpoints(): array
{
return [
'method' => 'POST',
'path' => 'endpoints',
'jsonKey' => 'endpoint',
'params' => [
'interface' => $this->params->interf(),
'name' => $this->isRequired($this->params->name('endpoint')),
'region' => $this->params->region(),
'url' => $this->params->endpointUrl(),
'serviceId' => $this->params->serviceId(),
],
];
}
public function getEndpoints(): array
{
return [
'method' => 'GET',
'path' => 'endpoints',
'params' => [
'interface' => $this->query($this->params->interf()),
'serviceId' => $this->query($this->params->serviceId()),
],
];
}
public function getEndpoint(): array
{
return [
'method' => 'GET',
'path' => 'endpoints/{id}',
'params' => [
'id' => $this->params->idUrl('service'),
],
];
}
public function patchEndpoint(): array
{
return [
'method' => 'PATCH',
'path' => 'endpoints/{id}',
'jsonKey' => 'endpoint',
'params' => [
'id' => $this->params->idUrl('endpoint'),
'interface' => $this->params->interf(),
'name' => $this->params->name('endpoint'),
'region' => $this->params->region(),
'url' => $this->params->endpointUrl(),
'serviceId' => $this->params->serviceId(),
],
];
}
public function deleteEndpoint(): array
{
return [
'method' => 'DELETE',
'path' => 'endpoints/{id}',
'params' => ['id' => $this->params->idUrl('endpoint')],
];
}
public function postDomains(): array
{
return [
'method' => 'POST',
'path' => 'domains',
'jsonKey' => 'domain',
'params' => [
'name' => $this->isRequired($this->params->name('domain')),
'enabled' => $this->params->enabled('domain'),
'description' => $this->params->desc('domain'),
],
];
}
public function getDomains(): array
{
return [
'method' => 'GET',
'path' => 'domains',
'params' => [
'name' => $this->query($this->params->name('domain')),
'enabled' => $this->query($this->params->enabled('domain')),
],
];
}
public function getDomain(): array
{
return [
'method' => 'GET',
'path' => 'domains/{id}',
'params' => ['id' => $this->params->idUrl('domain')],
];
}
public function patchDomain(): array
{
return [
'method' => 'PATCH',
'path' => 'domains/{id}',
'jsonKey' => 'domain',
'params' => [
'id' => $this->params->idUrl('domain'),
'name' => $this->params->name('domain'),
'enabled' => $this->params->enabled('domain'),
'description' => $this->params->desc('domain'),
],
];
}
public function deleteDomain(): array
{
return [
'method' => 'DELETE',
'path' => 'domains/{id}',
'params' => ['id' => $this->params->idUrl('domain')],
];
}
public function getUserRoles(): array
{
return [
'method' => 'GET',
'path' => 'domains/{domainId}/users/{userId}/roles',
'params' => [
'domainId' => $this->params->idUrl('domain'),
'userId' => $this->params->idUrl('user'),
],
];
}
public function putUserRoles(): array
{
return [
'method' => 'PUT',
'path' => 'domains/{domainId}/users/{userId}/roles/{roleId}',
'params' => [
'domainId' => $this->params->idUrl('domain'),
'userId' => $this->params->idUrl('user'),
'roleId' => $this->params->idUrl('role'),
],
];
}
public function headUserRole(): array
{
return [
'method' => 'HEAD',
'path' => 'domains/{domainId}/users/{userId}/roles/{roleId}',
'params' => [
'domainId' => $this->params->idUrl('domain'),
'userId' => $this->params->idUrl('user'),
'roleId' => $this->params->idUrl('role'),
],
];
}
public function deleteUserRole(): array
{
return [
'method' => 'DELETE',
'path' => 'domains/{domainId}/users/{userId}/roles/{roleId}',
'params' => [
'domainId' => $this->params->idUrl('domain'),
'userId' => $this->params->idUrl('user'),
'roleId' => $this->params->idUrl('role'),
],
];
}
public function getGroupRoles(): array
{
return [
'method' => 'GET',
'path' => 'domains/{domainId}/groups/{groupId}/roles',
'params' => [
'domainId' => $this->params->idUrl('domain'),
'groupId' => $this->params->idUrl('group'),
],
];
}
public function putGroupRole(): array
{
return [
'method' => 'PUT',
'path' => 'domains/{domainId}/groups/{groupId}/roles/{roleId}',
'params' => [
'domainId' => $this->params->idUrl('domain'),
'groupId' => $this->params->idUrl('group'),
'roleId' => $this->params->idUrl('role'),
],
];
}
public function headGroupRole(): array
{
return [
'method' => 'HEAD',
'path' => 'domains/{domainId}/groups/{groupId}/roles/{roleId}',
'params' => [
'domainId' => $this->params->idUrl('domain'),
'groupId' => $this->params->idUrl('group'),
'roleId' => $this->params->idUrl('role'),
],
];
}
public function deleteGroupRole(): array
{
return [
'method' => 'DELETE',
'path' => 'domains/{domainId}/groups/{groupId}/roles/{roleId}',
'params' => [
'domainId' => $this->params->idUrl('domain'),
'groupId' => $this->params->idUrl('group'),
'roleId' => $this->params->idUrl('role'),
],
];
}
public function postProjects(): array
{
return [
'method' => 'POST',
'path' => 'projects',
'jsonKey' => 'project',
'params' => [
'description' => $this->params->desc('project'),
'domainId' => $this->params->domainId('project'),
'parentId' => $this->params->parentId(),
'enabled' => $this->params->enabled('project'),
'name' => $this->isRequired($this->params->name('project')),
],
];
}
public function getProjects(): array
{
return [
'method' => 'GET',
'path' => 'projects',
'params' => [
'domainId' => $this->query($this->params->domainId('project')),
'enabled' => $this->query($this->params->enabled('project')),
'name' => $this->query($this->params->name('project')),
],
];
}
public function getProject(): array
{
return [
'method' => 'GET',
'path' => 'projects/{id}',
'params' => ['id' => $this->params->idUrl('project')],
];
}
public function patchProject(): array
{
return [
'method' => 'PATCH',
'path' => 'projects/{id}',
'jsonKey' => 'project',
'params' => [
'id' => $this->params->idUrl('project'),
'description' => $this->params->desc('project'),
'domainId' => $this->params->domainId('project'),
'parentId' => $this->params->parentId(),
'enabled' => $this->params->enabled('project'),
'name' => $this->params->name('project'),
],
];
}
public function deleteProject(): array
{
return [
'method' => 'DELETE',
'path' => 'projects/{id}',
'params' => ['id' => $this->params->idUrl('project')],
];
}
public function getProjectUserRoles(): array
{
return [
'method' => 'GET',
'path' => 'projects/{projectId}/users/{userId}/roles',
'params' => [
'projectId' => $this->params->idUrl('project'),
'userId' => $this->params->idUrl('user'),
],
];
}
public function putProjectUserRole(): array
{
return [
'method' => 'PUT',
'path' => 'projects/{projectId}/users/{userId}/roles/{roleId}',
'params' => [
'projectId' => $this->params->idUrl('project'),
'userId' => $this->params->idUrl('user'),
'roleId' => $this->params->idUrl('role'),
],
];
}
public function headProjectUserRole(): array
{
return [
'method' => 'HEAD',
'path' => 'projects/{projectId}/users/{userId}/roles/{roleId}',
'params' => [
'projectId' => $this->params->idUrl('project'),
'userId' => $this->params->idUrl('user'),
'roleId' => $this->params->idUrl('role'),
],
];
}
public function deleteProjectUserRole(): array
{
return [
'method' => 'DELETE',
'path' => 'projects/{projectId}/users/{userId}/roles/{roleId}',
'params' => [
'projectId' => $this->params->idUrl('project'),
'userId' => $this->params->idUrl('user'),
'roleId' => $this->params->idUrl('role'),
],
];
}
public function getProjectGroupRoles(): array
{
return [
'method' => 'GET',
'path' => 'projects/{projectId}/groups/{groupId}/roles',
'params' => [
'projectId' => $this->params->idUrl('project'),
'groupId' => $this->params->idUrl('group'),
],
];
}
public function putProjectGroupRole(): array
{
return [
'method' => 'PUT',
'path' => 'projects/{projectId}/groups/{groupId}/roles/{roleId}',
'params' => [
'projectId' => $this->params->idUrl('project'),
'groupId' => $this->params->idUrl('group'),
'roleId' => $this->params->idUrl('role'),
],
];
}
public function headProjectGroupRole(): array
{
return [
'method' => 'HEAD',
'path' => 'projects/{projectId}/groups/{groupId}/roles/{roleId}',
'params' => [
'projectId' => $this->params->idUrl('project'),
'groupId' => $this->params->idUrl('group'),
'roleId' => $this->params->idUrl('role'),
],
];
}
public function deleteProjectGroupRole(): array
{
return [
'method' => 'DELETE',
'path' => 'projects/{projectId}/groups/{groupId}/roles/{roleId}',
'params' => [
'projectId' => $this->params->idUrl('project'),
'groupId' => $this->params->idUrl('group'),
'roleId' => $this->params->idUrl('role'),
],
];
}
public function postUsers(): array
{
return [
'method' => 'POST',
'path' => 'users',
'jsonKey' => 'user',
'params' => [
'defaultProjectId' => $this->params->defaultProjectId(),
'description' => $this->params->desc('user'),
'domainId' => $this->params->domainId('user'),
'email' => $this->params->email(),
'enabled' => $this->params->enabled('user'),
'name' => $this->isRequired($this->params->name('user')),
'password' => $this->params->password(),
],
];
}
public function getUsers(): array
{
return [
'method' => 'GET',
'path' => 'users',
'params' => [
'domainId' => $this->query($this->params->domainId('user')),
'enabled' => $this->query($this->params->enabled('user')),
'name' => $this->query($this->params->name('user')),
],
];
}
public function getUser(): array
{
return [
'method' => 'GET',
'path' => 'users/{id}',
'params' => ['id' => $this->params->idUrl('user')],
];
}
public function patchUser(): array
{
return [
'method' => 'PATCH',
'path' => 'users/{id}',
'jsonKey' => 'user',
'params' => [
'id' => $this->params->idUrl('user'),
'defaultProjectId' => $this->params->defaultProjectId(),
'description' => $this->params->desc('user'),
'email' => $this->params->email(),
'enabled' => $this->params->enabled('user'),
'name' => $this->params->name('user'),
'password' => $this->params->password(),
],
];
}
public function deleteUser(): array
{
return [
'method' => 'DELETE',
'path' => 'users/{id}',
'params' => ['id' => $this->params->idUrl('user')],
];
}
public function getUserGroups(): array
{
return [
'method' => 'GET',
'path' => 'users/{id}/groups',
'params' => ['id' => $this->params->idUrl('user')],
];
}
public function getUserProjects(): array
{
return [
'method' => 'GET',
'path' => 'users/{id}/projects',
'params' => ['id' => $this->params->idUrl('user')],
];
}
public function postGroups(): array
{
return [
'method' => 'POST',
'path' => 'groups',
'jsonKey' => 'group',
'params' => [
'description' => $this->params->desc('group'),
'domainId' => $this->params->domainId('group'),
'name' => $this->params->name('group'),
],
];
}
public function getGroups(): array
{
return [
'method' => 'GET',
'path' => 'groups',
'params' => ['domainId' => $this->query($this->params->domainId('group'))],
];
}
public function getGroup(): array
{
return [
'method' => 'GET',
'path' => 'groups/{id}',
'params' => ['id' => $this->params->idUrl('group')],
];
}
public function patchGroup(): array
{
return [
'method' => 'PATCH',
'path' => 'groups/{id}',
'jsonKey' => 'group',
'params' => [
'id' => $this->params->idUrl('group'),
'description' => $this->params->desc('group'),
'name' => $this->params->name('group'),
],
];
}
public function deleteGroup(): array
{
return [
'method' => 'DELETE',
'path' => 'groups/{id}',
'params' => ['id' => $this->params->idUrl('group')],
];
}
public function getGroupUsers(): array
{
return [
'method' => 'GET',
'path' => 'groups/{id}/users',
'params' => ['id' => $this->params->idUrl('group')],
];
}
public function putGroupUser(): array
{
return [
'method' => 'PUT',
'path' => 'groups/{groupId}/users/{userId}',
'params' => [
'groupId' => $this->params->idUrl('group'),
'userId' => $this->params->idUrl('user'),
],
];
}
public function deleteGroupUser(): array
{
return [
'method' => 'DELETE',
'path' => 'groups/{groupId}/users/{userId}',
'params' => [
'groupId' => $this->params->idUrl('group'),
'userId' => $this->params->idUrl('user'),
],
];
}
public function headGroupUser(): array
{
return [
'method' => 'HEAD',
'path' => 'groups/{groupId}/users/{userId}',
'params' => [
'groupId' => $this->params->idUrl('group'),
'userId' => $this->params->idUrl('user'),
],
];
}
public function postCredentials(): array
{
return [
'method' => 'POST',
'path' => 'credentials',
'params' => [
'blob' => $this->params->blob(),
'projectId' => $this->params->projectId(),
'type' => $this->params->type('credential'),
'userId' => $this->params->userId(),
],
];
}
public function getCredentials(): array
{
return [
'method' => 'GET',
'path' => 'credentials',
'params' => [],
];
}
public function getCredential(): array
{
return [
'method' => 'GET',
'path' => 'credentials/{id}',
'params' => ['id' => $this->params->idUrl('credential')],
];
}
public function patchCredential(): array
{
return [
'method' => 'PATCH',
'path' => 'credentials/{id}',
'params' => ['id' => $this->params->idUrl('credential')] + $this->postCredentials()['params'],
];
}
public function deleteCredential(): array
{
return [
'method' => 'DELETE',
'path' => 'credentials/{id}',
'params' => ['id' => $this->params->idUrl('credential')],
];
}
public function postRoles(): array
{
return [
'method' => 'POST',
'path' => 'roles',
'jsonKey' => 'role',
'params' => ['name' => $this->isRequired($this->params->name('role'))],
];
}
public function getRoles(): array
{
return [
'method' => 'GET',
'path' => 'roles',
'params' => ['name' => $this->query($this->params->name('role'))],
];
}
public function deleteRole(): array
{
return [
'method' => 'DELETE',
'path' => 'roles/{id}',
'params' => ['id' => $this->params->idUrl('role')],
];
}
public function getRoleAssignments(): array
{
return [
'method' => 'GET',
'path' => 'role_assignments',
'params' => [
'userId' => $this->params->userIdQuery(),
'groupId' => $this->params->groupIdQuery(),
'roleId' => $this->params->roleIdQuery(),
'domainId' => $this->params->domainIdQuery(),
'projectId' => $this->params->projectIdQuery(),
'effective' => $this->params->effective(),
],
];
}
public function postPolicies(): array
{
return [
'method' => 'POST',
'path' => 'policies',
'jsonKey' => 'policy',
'params' => [
'blob' => $this->params->blob(),
'projectId' => $this->params->projectId('policy'),
'type' => $this->params->type('policy'),
'userId' => $this->params->userId('policy'),
],
];
}
public function getPolicies(): array
{
return [
'method' => 'GET',
'path' => 'policies',
'params' => ['type' => $this->query($this->params->type('policy'))],
];
}
public function getPolicy(): array
{
return [
'method' => 'GET',
'path' => 'policies/{id}',
'params' => ['id' => $this->params->idUrl('policy')],
];
}
public function patchPolicy(): array
{
return [
'method' => 'PATCH',
'path' => 'policies/{id}',
'jsonKey' => 'policy',
'params' => [
'id' => $this->params->idUrl('policy'),
'blob' => $this->params->blob(),
'projectId' => $this->params->projectId('policy'),
'type' => $this->params->type('policy'),
'userId' => $this->params->userId(),
],
];
}
public function deletePolicy(): array
{
return [
'method' => 'DELETE',
'path' => 'policies/{id}',
'params' => ['id' => $this->params->idUrl('policy')],
];
}
public function getApplicationCredential(): array
{
return [
'method' => 'GET',
'path' => 'users/{userId}/application_credentials/{id}',
'jsonKey' => 'application_credential',
'params' => [
'id' => $this->params->idUrl('application_credential'),
'userId' => $this->params->idUrl('user'),
],
];
}
public function postApplicationCredential(): array
{
return [
'method' => 'POST',
'path' => 'users/{userId}/application_credentials',
'jsonKey' => 'application_credential',
'params' => [
'userId' => $this->params->idUrl('user'),
'name' => $this->params->name('application_credential'),
'description' => $this->params->desc('application_credential'),
],
];
}
public function deleteApplicationCredential(): array
{
return [
'method' => 'DELETE',
'path' => 'users/{userId}/application_credentials/{id}',
'params' => [
'id' => $this->params->idUrl('application_credential'),
'userId' => $this->params->idUrl('user'),
],
];
}
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3;
abstract class Enum
{
public const INTERFACE_INTERNAL = 'internal';
public const INTERFACE_PUBLIC = 'public';
public const INTERFACE_ADMIN = 'admin';
}
@@ -0,0 +1,69 @@
<?php
namespace OpenStack\Identity\v3\Models;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
/**
* @property \OpenStack\Identity\v3\Api $api
*/
class ApplicationCredential extends OperatorResource implements Creatable, Listable, Retrievable, Deletable
{
/** @var string */
public $id;
/** @var string */
public $userId;
/** @var string */
public $name;
/** @var string */
public $description;
/** @var string|null */
public $secret = null;
protected $aliases = [
'user_id' => 'userId',
];
protected $resourceKey = 'application_credential';
protected $resourcesKey = 'application_credentials';
/**
* {@inheritdoc}
*
* @param array $userOptions {@see \OpenStack\Identity\v3\Api::postApplicationCredential}
*/
public function create(array $userOptions): Creatable
{
$response = $this->execute($this->api->postApplicationCredential(), $userOptions);
return $this->populateFromResponse($response);
}
/**
* {@inheritdoc}
*/
public function retrieve()
{
$response = $this->execute(
$this->api->getApplicationCredential(),
['id' => $this->id, 'userId' => $this->userId]
);
$this->populateFromResponse($response);
}
/**
* {@inheritdoc}
*/
public function delete()
{
$this->executeWithState($this->api->deleteApplicationCredential());
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3\Models;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
class Assignment extends OperatorResource implements Listable
{
/** @var Role */
public $role;
/** @var array */
public $scope;
/** @var Group */
public $group;
/** @var User */
public $user;
protected $resourcesKey = 'role_assignments';
protected $resourceKey = 'role_assignment';
protected function getAliases(): array
{
return parent::getAliases() + [
'role' => new Alias('role', Role::class),
'user' => new Alias('user', User::class),
'group' => new Alias('group', Group::class),
];
}
}
@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3\Models;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\OperatorResource;
/**
* @property \OpenStack\Identity\v3\Api $api
*/
class Catalog extends OperatorResource implements \OpenStack\Common\Auth\Catalog
{
/** @var []Service */
public $services;
protected function getAliases(): array
{
return parent::getAliases() + [
'services' => new Alias('services', Service::class, true),
];
}
public function populateFromArray(array $data): self
{
foreach ($data as $service) {
$this->services[] = $this->model(Service::class, $service);
}
return $this;
}
/**
* Retrieve a base URL for a service, according to its catalog name, type, region.
*
* @param string $name the name of the service as it appears in the catalog
* @param string $type the type of the service as it appears in the catalog
* @param string $region the region of the service as it appears in the catalog
* @param string $urlType unused
*
* @return false|string FALSE if no URL found
*/
public function getServiceUrl(string $name, string $type, string $region, string $urlType): string
{
if (empty($this->services)) {
throw new \RuntimeException('No services are defined');
}
foreach ($this->services as $service) {
if (false !== ($url = $service->getUrl($name, $type, $region, $urlType))) {
return $url;
}
}
throw new \RuntimeException(sprintf("Endpoint URL could not be found in the catalog for this service.\nName: %s\nType: %s\nRegion: %s\nURL type: %s", $name, $type, $region, $urlType));
}
}
@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3\Models;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
/**
* @property \OpenStack\Identity\v3\Api $api
*/
class Credential extends OperatorResource implements Creatable, Updateable, Retrievable, Listable, Deletable
{
/** @var string */
public $blob;
/** @var string */
public $id;
/** @var array */
public $links;
/** @var string */
public $projectId;
/** @var string */
public $type;
/** @var string */
public $userId;
protected $aliases = [
'project_id' => 'projectId',
'user_id' => 'userId',
];
public function create(array $userOptions): Creatable
{
$response = $this->execute($this->api->postCredentials(), $userOptions);
return $this->populateFromResponse($response);
}
public function retrieve()
{
$response = $this->executeWithState($this->api->getCredential());
$this->populateFromResponse($response);
}
public function update()
{
$response = $this->executeWithState($this->api->patchCredential());
$this->populateFromResponse($response);
}
public function delete()
{
$this->executeWithState($this->api->deleteCredential());
}
}
@@ -0,0 +1,148 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3\Models;
use OpenStack\Common\Error\BadResponseError;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
/**
* @property \OpenStack\Identity\v3\Api $api
*/
class Domain extends OperatorResource implements Creatable, Listable, Retrievable, Updateable, Deletable
{
/** @var string */
public $id;
/** @var string */
public $name;
/** @var array */
public $links;
/** @var bool */
public $enabled;
/** @var string */
public $description;
protected $resourceKey = 'domain';
protected $resourcesKey = 'domains';
/**
* @param array $data {@see \OpenStack\Identity\v3\Api::postDomains}
*/
public function create(array $data): Creatable
{
$response = $this->execute($this->api->postDomains(), $data);
return $this->populateFromResponse($response);
}
public function retrieve()
{
$response = $this->executeWithState($this->api->getDomain());
$this->populateFromResponse($response);
}
public function update()
{
$response = $this->executeWithState($this->api->patchDomain());
$this->populateFromResponse($response);
}
public function delete()
{
$this->executeWithState($this->api->deleteDomain());
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::getUserRoles}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Role>
*/
public function listUserRoles(array $options = []): \Generator
{
$options['domainId'] = $this->id;
return $this->model(Role::class)->enumerate($this->api->getUserRoles(), $options);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::putUserRoles}
*/
public function grantUserRole(array $options = [])
{
$this->execute($this->api->putUserRoles(), ['domainId' => $this->id] + $options);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::headUserRole}
*/
public function checkUserRole(array $options = []): bool
{
try {
$this->execute($this->api->headUserRole(), ['domainId' => $this->id] + $options);
return true;
} catch (BadResponseError $e) {
return false;
}
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::deleteUserRole}
*/
public function revokeUserRole(array $options = [])
{
$this->execute($this->api->deleteUserRole(), ['domainId' => $this->id] + $options);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::getGroupRoles}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Role>
*/
public function listGroupRoles(array $options = []): \Generator
{
$options['domainId'] = $this->id;
return $this->model(Role::class)->enumerate($this->api->getGroupRoles(), $options);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::putGroupRole}
*/
public function grantGroupRole(array $options = [])
{
$this->execute($this->api->putGroupRole(), ['domainId' => $this->id] + $options);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::headGroupRole}
*/
public function checkGroupRole(array $options = []): bool
{
try {
$this->execute($this->api->headGroupRole(), ['domainId' => $this->id] + $options);
return true;
} catch (BadResponseError $e) {
return false;
}
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::deleteGroupRole}
*/
public function revokeGroupRole(array $options = [])
{
$this->execute($this->api->deleteGroupRole(), ['domainId' => $this->id] + $options);
}
}
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3\Models;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
/**
* @property \OpenStack\Identity\v3\Api $api
*/
class Endpoint extends OperatorResource implements Creatable, Updateable, Deletable, Retrievable
{
/** @var string */
public $id;
/** @var string */
public $interface;
/** @var string */
public $name;
/** @var string */
public $serviceId;
/** @var string */
public $region;
/** @var array */
public $links;
/** @var string */
public $url;
protected $resourceKey = 'endpoint';
protected $resourcesKey = 'endpoints';
protected $aliases = ['service_id' => 'serviceId'];
/**
* @param array $data {@see \OpenStack\Identity\v3\Api::postEndpoints}
*/
public function create(array $data): Creatable
{
$response = $this->execute($this->api->postEndpoints(), $data);
return $this->populateFromResponse($response);
}
public function retrieve()
{
$response = $this->executeWithState($this->api->getEndpoint());
$this->populateFromResponse($response);
}
public function update()
{
$response = $this->executeWithState($this->api->patchEndpoint());
$this->populateFromResponse($response);
}
public function delete()
{
$this->execute($this->api->deleteEndpoint(), $this->getAttrs(['id']));
}
public function regionMatches(string $value): bool
{
return in_array($this->region, ['*', $value]);
}
public function interfaceMatches(string $value): bool
{
return $this->interface && $this->interface == $value;
}
}
@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3\Models;
use OpenStack\Common\Error\BadResponseError;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
/**
* @property \OpenStack\Identity\v3\Api $api
*/
class Group extends OperatorResource implements Creatable, Listable, Retrievable, Updateable, Deletable
{
/** @var string */
public $domainId;
/** @var string */
public $id;
/** @var string */
public $description;
/** @var array */
public $links;
/** @var string */
public $name;
protected $aliases = ['domain_id' => 'domainId'];
protected $resourceKey = 'group';
protected $resourcesKey = 'groups';
/**
* @param array $data {@see \OpenStack\Identity\v3\Api::postGroups}
*/
public function create(array $data): Creatable
{
$response = $this->execute($this->api->postGroups(), $data);
return $this->populateFromResponse($response);
}
public function retrieve()
{
$response = $this->execute($this->api->getGroup(), ['id' => $this->id]);
$this->populateFromResponse($response);
}
public function update()
{
$response = $this->executeWithState($this->api->patchGroup());
$this->populateFromResponse($response);
}
public function delete()
{
$this->execute($this->api->deleteGroup(), ['id' => $this->id]);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::getGroupUsers}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\User>
*/
public function listUsers(array $options = []): \Generator
{
$options['id'] = $this->id;
return $this->model(User::class)->enumerate($this->api->getGroupUsers(), $options);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::putGroupUser}
*/
public function addUser(array $options)
{
$this->execute($this->api->putGroupUser(), ['groupId' => $this->id] + $options);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::deleteGroupUser}
*/
public function removeUser(array $options)
{
$this->execute($this->api->deleteGroupUser(), ['groupId' => $this->id] + $options);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::headGroupUser}
*/
public function checkMembership(array $options): bool
{
try {
$this->execute($this->api->headGroupUser(), ['groupId' => $this->id] + $options);
return true;
} catch (BadResponseError $e) {
return false;
}
}
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3\Models;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
/**
* @property \OpenStack\Identity\v3\Api $api
*/
class Policy extends OperatorResource implements Creatable, Listable, Retrievable, Updateable, Deletable
{
/** @var string */
public $blob;
/** @var string */
public $id;
/** @var array */
public $links;
/** @var string */
public $projectId;
/** @var string */
public $type;
/** @var string */
public $userId;
protected $resourceKey = 'policy';
protected $resourcesKey = 'policies';
protected $aliases = [
'project_id' => 'projectId',
'user_id' => 'userId',
];
/**
* @param array $data {@see \OpenStack\Identity\v3\Api::postPolicies}
*/
public function create(array $data): Creatable
{
$response = $this->execute($this->api->postPolicies(), $data);
return $this->populateFromResponse($response);
}
public function retrieve()
{
$response = $this->execute($this->api->getPolicy(), ['id' => $this->id]);
$this->populateFromResponse($response);
}
public function update()
{
$response = $this->executeWithState($this->api->patchPolicy());
$this->populateFromResponse($response);
}
public function delete()
{
$this->execute($this->api->deletePolicy(), ['id' => $this->id]);
}
}
@@ -0,0 +1,160 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3\Models;
use OpenStack\Common\Error\BadResponseError;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
/**
* @property \OpenStack\Identity\v3\Api $api
*/
class Project extends OperatorResource implements Creatable, Retrievable, Listable, Updateable, Deletable
{
/** @var string */
public $domainId;
/** @var string */
public $parentId;
/** @var bool */
public $enabled;
/** @var string */
public $description;
/** @var string */
public $id;
/** @var array */
public $links;
/** @var string */
public $name;
protected $aliases = [
'domain_id' => 'domainId',
'parent_id' => 'parentId',
];
protected $resourceKey = 'project';
protected $resourcesKey = 'projects';
/**
* @param array $data {@see \OpenStack\Identity\v3\Api::postProjects}
*/
public function create(array $data): Creatable
{
$response = $this->execute($this->api->postProjects(), $data);
$this->populateFromResponse($response);
return $this;
}
public function retrieve()
{
$response = $this->executeWithState($this->api->getProject());
$this->populateFromResponse($response);
}
public function update()
{
$response = $this->executeWithState($this->api->patchProject());
$this->populateFromResponse($response);
}
public function delete()
{
$this->executeWithState($this->api->deleteProject());
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::getProjectUserRoles}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Role>
*/
public function listUserRoles(array $options = []): \Generator
{
$options['projectId'] = $this->id;
return $this->model(Role::class)->enumerate($this->api->getProjectUserRoles(), $options);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::putProjectUserRole}
*/
public function grantUserRole(array $options)
{
$this->execute($this->api->putProjectUserRole(), ['projectId' => $this->id] + $options);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::headProjectUserRole}
*/
public function checkUserRole(array $options): bool
{
try {
$this->execute($this->api->headProjectUserRole(), ['projectId' => $this->id] + $options);
return true;
} catch (BadResponseError $e) {
return false;
}
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::deleteProjectUserRole}
*/
public function revokeUserRole(array $options)
{
$this->execute($this->api->deleteProjectUserRole(), ['projectId' => $this->id] + $options);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::getProjectGroupRoles}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Role>
*/
public function listGroupRoles(array $options = []): \Generator
{
$options['projectId'] = $this->id;
return $this->model(Role::class)->enumerate($this->api->getProjectGroupRoles(), $options);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::putProjectGroupRole}
*/
public function grantGroupRole(array $options)
{
$this->execute($this->api->putProjectGroupRole(), ['projectId' => $this->id] + $options);
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::headProjectGroupRole}
*/
public function checkGroupRole(array $options): bool
{
try {
$this->execute($this->api->headProjectGroupRole(), ['projectId' => $this->id] + $options);
return true;
} catch (BadResponseError $e) {
return false;
}
}
/**
* @param array $options {@see \OpenStack\Identity\v3\Api::deleteProjectGroupRole}
*/
public function revokeGroupRole(array $options)
{
$this->execute($this->api->deleteProjectGroupRole(), ['projectId' => $this->id] + $options);
}
}
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3\Models;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
/**
* @property \OpenStack\Identity\v3\Api $api
*/
class Role extends OperatorResource implements Creatable, Listable, Deletable
{
/** @var string */
public $id;
/** @var string */
public $name;
/** @var array */
public $links;
protected $resourceKey = 'role';
protected $resourcesKey = 'roles';
/**
* @param array $data {@see \OpenStack\Identity\v3\Api::postRoles}
*/
public function create(array $data): Creatable
{
$response = $this->execute($this->api->postRoles(), $data);
return $this->populateFromResponse($response);
}
public function delete()
{
$this->executeWithState($this->api->deleteRole());
}
}
@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3\Models;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
/**
* @property \OpenStack\Identity\v3\Api $api
*/
class Service extends OperatorResource implements Creatable, Listable, Retrievable, Updateable, Deletable
{
/** @var string */
public $id;
/** @var string */
public $name;
/** @var string */
public $type;
/** @var string */
public $description;
/** @var []Endpoint */
public $endpoints;
/** @var array */
public $links;
protected $resourceKey = 'service';
protected $resourcesKey = 'services';
protected function getAliases(): array
{
return parent::getAliases() + [
'endpoints' => new Alias('endpoints', Endpoint::class, true),
];
}
/**
* @param array $data {@see \OpenStack\Identity\v3\Api::postServices}
*/
public function create(array $data): Creatable
{
$response = $this->execute($this->api->postServices(), $data);
return $this->populateFromResponse($response);
}
public function retrieve()
{
$response = $this->executeWithState($this->api->getService());
$this->populateFromResponse($response);
}
public function update()
{
$response = $this->executeWithState($this->api->patchService());
$this->populateFromResponse($response);
}
public function delete()
{
$this->executeWithState($this->api->deleteService());
}
private function nameMatches(string $value): bool
{
return $this->name && $this->name == $value;
}
private function typeMatches(string $value): bool
{
return $this->type && $this->type == $value;
}
/**
* Retrieve the base URL for a service.
*
* @param string $name the name of the service as it appears in the catalog
* @param string $type the type of the service as it appears in the catalog
* @param string $region the region of the service as it appears in the catalog
* @param string $interface the interface of the service as it appears in the catalog
*
* @return string|false
*/
public function getUrl(string $name, string $type, string $region, string $interface)
{
if (!$this->nameMatches($name) || !$this->typeMatches($type)) {
return false;
}
foreach ($this->endpoints as $endpoint) {
if ($endpoint->regionMatches($region) && $endpoint->interfaceMatches($interface)) {
return $endpoint->url;
}
}
return false;
}
}
@@ -0,0 +1,150 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3\Models;
use InvalidArgumentException;
use OpenStack\Common\Error\BadResponseError;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Transport\Utils;
use Psr\Http\Message\ResponseInterface;
/**
* @property \OpenStack\Identity\v3\Api $api
*/
class Token extends OperatorResource implements Creatable, Retrievable, \OpenStack\Common\Auth\Token
{
/** @var array */
public $methods;
/** @var Role[] */
public $roles;
/** @var \DateTimeImmutable */
public $expires;
/** @var Project */
public $project;
/** @var Catalog */
public $catalog;
public $extras;
/** @var User */
public $user;
/** @var \DateTimeImmutable */
public $issued;
/** @var string */
public $id;
protected $resourceKey = 'token';
protected $resourcesKey = 'tokens';
protected $cachedToken;
protected function getAliases(): array
{
return parent::getAliases() + [
'roles' => new Alias('roles', Role::class, true),
'expires_at' => new Alias('expires', \DateTimeImmutable::class),
'project' => new Alias('project', Project::class),
'catalog' => new Alias('catalog', Catalog::class),
'user' => new Alias('user', User::class),
'issued_at' => new Alias('issued', \DateTimeImmutable::class),
];
}
public function populateFromResponse(ResponseInterface $response)
{
parent::populateFromResponse($response);
$this->id = $response->getHeaderLine('X-Subject-Token');
return $this;
}
public function getId(): string
{
return $this->id;
}
/**
* @return bool TRUE if the token has expired (and is invalid); FALSE otherwise
*/
public function hasExpired(): bool
{
return $this->expires <= new \DateTimeImmutable('now', $this->expires->getTimezone());
}
public function retrieve()
{
$response = $this->execute($this->api->getTokens(), ['tokenId' => $this->id]);
$this->populateFromResponse($response);
}
/**
* @param array $userOptions {@see \OpenStack\Identity\v3\Api::postTokens}
*/
public function create(array $userOptions): Creatable
{
if (isset($userOptions['user'])) {
$userOptions['methods'] = ['password'];
if (!isset($userOptions['user']['id']) && empty($userOptions['user']['domain'])) {
throw new InvalidArgumentException('When authenticating with a username, you must also provide either the domain name '.'or domain ID to which the user belongs to. Alternatively, if you provide a user ID instead, '.'you do not need to provide domain information.');
}
} elseif (isset($userOptions['application_credential'])) {
$userOptions['methods'] = ['application_credential'];
if (!isset($userOptions['application_credential']['id']) || !isset($userOptions['application_credential']['secret'])) {
throw new InvalidArgumentException('When authenticating with a application_credential, you must provide application credential ID '.' and application credential secret.');
}
} elseif (isset($userOptions['tokenId'])) {
$userOptions['methods'] = ['token'];
} else {
throw new InvalidArgumentException('Either a user, tokenId or application_credential must be provided.');
}
$response = $this->execute($this->api->postTokens(), $userOptions);
$token = $this->populateFromResponse($response);
// Cache response as an array to export if needed.
// Added key `id` which is auth token from HTTP header X-Subject-Token
$this->cachedToken = Utils::flattenJson(Utils::jsonDecode($response), $this->resourceKey);
$this->cachedToken['id'] = $token->id;
return $token;
}
/**
* Returns a serialized representation of an authentication token.
*
* Initialize OpenStack object using $params['cachedToken'] to reduce the amount of HTTP calls.
*
* This array is a modified version of response from `/auth/tokens`. Do not manually modify this array.
*/
public function export(): array
{
return $this->cachedToken;
}
/**
* Checks if the token is valid.
*
* @return bool TRUE if the token is valid; FALSE otherwise
*/
public function validate(): bool
{
try {
$this->execute($this->api->headTokens(), ['tokenId' => $this->id]);
return true;
} catch (BadResponseError $e) {
return false;
}
}
}
@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3\Models;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Resource\Updateable;
/**
* @property \OpenStack\Identity\v3\Api $api
*/
class User extends OperatorResource implements Creatable, Listable, Retrievable, Updateable, Deletable
{
/** @var string */
public $domainId;
/** @var string */
public $defaultProjectId;
/** @var string */
public $id;
/** @var string */
public $email;
/** @var bool */
public $enabled;
/** @var string */
public $description;
/** @var array */
public $links;
/** @var string */
public $name;
protected $aliases = [
'domain_id' => 'domainId',
'default_project_id' => 'defaultProjectId',
];
protected $resourceKey = 'user';
protected $resourcesKey = 'users';
/**
* @param array $data {@see \OpenStack\Identity\v3\Api::postUsers}
*/
public function create(array $data): Creatable
{
$response = $this->execute($this->api->postUsers(), $data);
return $this->populateFromResponse($response);
}
public function retrieve()
{
$response = $this->execute($this->api->getUser(), ['id' => $this->id]);
$this->populateFromResponse($response);
}
public function update()
{
$response = $this->executeWithState($this->api->patchUser());
$this->populateFromResponse($response);
}
public function delete()
{
$this->execute($this->api->deleteUser(), ['id' => $this->id]);
}
/**
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Group>
*/
public function listGroups(): \Generator
{
return $this->model(Group::class)->enumerate($this->api->getUserGroups(), ['id' => $this->id]);
}
/**
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Project>
*/
public function listProjects(): \Generator
{
return $this->model(Project::class)->enumerate($this->api->getUserProjects(), ['id' => $this->id]);
}
/**
* Creates a new application credential according to the provided options.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::postApplicationCredential}
*/
public function createApplicationCredential(array $options): ApplicationCredential
{
return $this->model(ApplicationCredential::class)->create(['userId' => $this->id] + $options);
}
/**
* Retrieves an application credential object and populates its unique identifier object. This operation will not
* perform a GET or HEAD request by default; you will need to call retrieve() if you want to pull in remote state
* from the API.
*/
public function getApplicationCredential(string $id): ApplicationCredential
{
return $this->model(ApplicationCredential::class, ['id' => $id, 'userId' => $this->id]);
}
}
@@ -0,0 +1,336 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3;
use OpenStack\Common\Api\AbstractParams;
class Params extends AbstractParams
{
public function methods(): array
{
return [
'type' => self::ARRAY_TYPE,
'path' => 'auth.identity',
'items' => ['type' => self::STRING_TYPE],
'description' => <<<EOT
An array of authentication methods (in string form) that the SDK will use to authenticate. The only acceptable methods
are "password" or "token".
EOT
];
}
public function applicationCredential(): array
{
return [
'type' => self::OBJECT_TYPE,
'path' => 'auth.identity',
'properties' => [
'id' => [
'type' => self::STRING_TYPE,
'description' => $this->id('application credential id'),
],
'secret' => [
'type' => self::STRING_TYPE,
'description' => 'The secret of the application credential',
],
],
];
}
public function user(): array
{
return [
'type' => self::OBJECT_TYPE,
'path' => 'auth.identity.password',
'properties' => [
'id' => [
'type' => self::STRING_TYPE,
'description' => $this->id('user'),
],
'name' => [
'type' => self::STRING_TYPE,
'description' => 'The username of the user',
],
'password' => [
'type' => self::STRING_TYPE,
'description' => 'The password of the user',
],
'domain' => $this->domain(),
],
];
}
public function tokenBody(): array
{
return [
'path' => 'auth.identity.token',
'sentAs' => 'id',
'type' => self::STRING_TYPE,
'description' => $this->id('token'),
];
}
public function scope(): array
{
return [
'type' => self::OBJECT_TYPE,
'path' => 'auth',
'properties' => [
'project' => $this->project(),
'domain' => $this->domain(),
],
];
}
public function typeQuery(): array
{
return [
'type' => 'string',
'location' => 'query',
'description' => 'Filters all the available services according to a given type',
];
}
public function interf(): array
{
return [
'description' => <<<EOT
Denotes the type of visibility the endpoint will have. Acceptable values are "admin", "public" or "internal". Admin
endpoints are only accessible to users who have authenticated with an admin role. Public endpoints are available to
all users and use a public IP. Internal endpoints are available to all users, but only via an internal, private IP.
EOT
];
}
public function region(): array
{
return [
'description' => <<<EOT
Denotes the geographic location that the endpoint will serve traffic from. This provides greater redundancy and also
offers better latency to your regions, but will require the system administrator to set up.
EOT
];
}
public function endpointUrl(): array
{
return [
'description' => <<<EOT
The HTTP or HTTPS URL that clients will communicate with when accessing your service endpoint.
EOT
];
}
public function serviceId(): array
{
return [
'type' => 'string',
'sentAs' => 'service_id',
'description' => $this->id('service')['description'].' that this endpoint belongs to',
];
}
public function password(): array
{
return [
'description' => <<<EOT
The password for the user that they will use to authenticate with. Please ensure it is sufficiently long and random. If
you want a password generated for you, you can use TODO.
EOT
];
}
public function email(): array
{
return [
'description' => 'The personal e-mail address of the user',
];
}
public function effective(): array
{
return [
'type' => self::BOOL_TYPE,
'location' => self::QUERY,
'description' => <<<EOT
Use the effective query parameter to list effective assignments at the user, project, and domain level. This parameter
allows for the effects of group membership. The group role assignment entities themselves are not returned in the
collection. This represents the effective role assignments that would be included in a scoped token. You can use the
other query parameters with the effective parameter.
For example, to determine what a user can actually do, issue this request: GET /role_assignments?user.id={user_id}&effective
To return the equivalent set of role assignments that would be included in the token response of a project-scoped
token, issue: GET /role_assignments?user.id={user_id}&scope.project.id={project_id}&effective
EOT
];
}
public function projectIdQuery(): array
{
return [
'sentAs' => 'scope.project.id',
'location' => 'query',
'description' => 'Filter by project ID',
];
}
public function domainIdQuery(): array
{
return [
'sentAs' => 'scope.domain.id',
'location' => 'query',
'description' => $this->id('domain')['description'].' associated with the role assignments',
];
}
public function roleIdQuery(): array
{
return [
'sentAs' => 'role.id',
'location' => 'query',
'description' => 'Filter by role ID',
];
}
public function groupIdQuery(): array
{
return [
'sentAs' => 'group.id',
'location' => 'query',
'description' => 'Filter by group ID',
];
}
public function userIdQuery(): array
{
return [
'sentAs' => 'user.id',
'location' => 'query',
'description' => 'Filter by user ID',
];
}
public function domain(): array
{
return [
'type' => 'object',
'properties' => [
'id' => $this->id('domain'),
'name' => $this->name('domain'),
],
];
}
public function project(): array
{
return [
'type' => 'object',
'properties' => [
'id' => $this->id('project'),
'name' => $this->name('project'),
'domain' => $this->domain(),
],
];
}
public function idUrl($type)
{
return [
'required' => true,
'location' => self::URL,
'description' => sprintf('The unique ID, or identifier, for the %s', $type),
];
}
public function tokenId(): array
{
return [
'location' => self::HEADER,
'sentAs' => 'X-Subject-Token',
'description' => 'The unique token ID',
];
}
public function domainId($type)
{
return [
'sentAs' => 'domain_id',
'description' => sprintf('%s associated with this %s', $this->id('domain')['description'], $type),
];
}
public function parentId(): array
{
return [
'sentAs' => 'parent_id',
'description' => <<<EOT
The unique ID of the project which serves as the parent for this project. For more information about hierarchical
multitenancy in Keystone v3, see: http://specs.openstack.org/openstack/keystone-specs/specs/juno/hierarchical_multitenancy.html
EOT
];
}
public function type($resource)
{
return [
'description' => sprintf('The type of the %s', $resource),
];
}
public function desc($resource)
{
return [
'description' => sprintf('A human-friendly summary that explains what the %s does', $resource),
];
}
public function enabled($resource)
{
return [
'type' => self::BOOL_TYPE,
'description' => sprintf(
'Indicates whether this %s is enabled or not. If not, the %s will be unavailable for use.',
$resource,
$resource
),
];
}
public function defaultProjectId(): array
{
return [
'sentAs' => 'default_project_id',
'description' => <<<EOT
The unique ID of the project which will serve as a default for the user. Unless another project ID is specified in an
API operation, it is assumed that this project was meant - and so it is used as a default throughout.
EOT
];
}
public function projectId(): array
{
return [
'sentAs' => 'project_id',
'description' => $this->id('project'),
];
}
public function userId(): array
{
return [
'sentAs' => 'user_id',
'description' => $this->id('user'),
];
}
public function blob(): array
{
return [
'type' => 'string',
'description' => "This does something, but it's not explained in the docs (as of writing this)",
];
}
}
@@ -0,0 +1,430 @@
<?php
declare(strict_types=1);
namespace OpenStack\Identity\v3;
use GuzzleHttp\ClientInterface;
use OpenStack\Common\Auth\IdentityService;
use OpenStack\Common\Error\BadResponseError;
use OpenStack\Common\Service\AbstractService;
/**
* Represents the Keystone v3 service.
*
* @property Api $api
*/
class Service extends AbstractService implements IdentityService
{
public static function factory(ClientInterface $client): self
{
return new static($client, new Api());
}
/**
* Authenticates credentials, giving back a token and a base URL for the service.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::postTokens}
*
* @return array Returns a {@see Models\Token} as the first element, a string base URL as the second
*/
public function authenticate(array $options): array
{
$authOptions = array_intersect_key($options, $this->api->postTokens()['params']);
if (!empty($options['cachedToken'])) {
$token = $this->generateTokenFromCache($options['cachedToken']);
if ($token->hasExpired()) {
throw new \RuntimeException(sprintf('Cached token has expired on "%s".', $token->expires->format(\DateTime::ISO8601)));
}
} else {
$token = $this->generateToken($authOptions);
}
$name = $options['catalogName'];
$type = $options['catalogType'];
$region = $options['region'];
$interface = $options['interface'] ?? Enum::INTERFACE_PUBLIC;
if ($baseUrl = $token->catalog->getServiceUrl($name, $type, $region, $interface)) {
return [$token, $baseUrl];
}
throw new \RuntimeException(sprintf('No service found with type [%s] name [%s] region [%s] interface [%s]', $type, $name, $region, $interface));
}
/**
* Generates authentication token from cached token using `$token->export()`.
*
* @param array $cachedToken {@see \OpenStack\Identity\v3\Models\Token::export}
*/
public function generateTokenFromCache(array $cachedToken): Models\Token
{
return $this->model(Models\Token::class)->populateFromArray($cachedToken);
}
/**
* Generates a new authentication token.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::postTokens}
*/
public function generateToken(array $options): Models\Token
{
return $this->model(Models\Token::class)->create($options);
}
/**
* Retrieves a token object and populates its unique identifier object. This operation will not perform a GET or
* HEAD request by default; you will need to call retrieve() if you want to pull in remote state from the API.
*
* @param string $id The unique ID of the token to retrieve
*/
public function getToken(string $id): Models\Token
{
return $this->model(Models\Token::class, ['id' => $id]);
}
/**
* Validates a token, identified by its ID, and returns TRUE if its valid, FALSE if not.
*
* @param string $id The unique ID of the token
*/
public function validateToken(string $id): bool
{
try {
$this->execute($this->api->headTokens(), ['tokenId' => $id]);
return true;
} catch (BadResponseError $e) {
return false;
}
}
/**
* Revokes a token, identified by its ID. After this operation completes, users will not be able to use this token
* again for authentication.
*
* @param string $id The unique ID of the token
*/
public function revokeToken(string $id)
{
$this->execute($this->api->deleteTokens(), ['tokenId' => $id]);
}
/**
* Creates a new service according to the provided options.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::postServices}
*/
public function createService(array $options): Models\Service
{
return $this->model(Models\Service::class)->create($options);
}
/**
* Returns a generator which will yield a collection of service objects. The elements which generators yield can be
* accessed using a foreach loop. Often the API will not return the full state of the resource in collections; you
* will need to use retrieve() to pull in the full state of the remote resource from the API.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::getServices}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Service>
*/
public function listServices(array $options = []): \Generator
{
return $this->model(Models\Service::class)->enumerate($this->api->getServices(), $options);
}
/**
* Retrieves a service object and populates its unique identifier object. This operation will not perform a GET or
* HEAD request by default; you will need to call retrieve() if you want to pull in remote state from the API.
*
* @param string $id The unique ID of the service
*/
public function getService(string $id): Models\Service
{
return $this->model(Models\Service::class, ['id' => $id]);
}
/**
* Creates a new endpoint according to the provided options.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::postEndpoints}
*/
public function createEndpoint(array $options): Models\Endpoint
{
return $this->model(Models\Endpoint::class)->create($options);
}
/**
* Retrieves an endpoint object and populates its unique identifier object. This operation will not perform a GET or
* HEAD request by default; you will need to call retrieve() if you want to pull in remote state from the API.
*
* @param string $id The unique ID of the service
*/
public function getEndpoint(string $id): Models\Endpoint
{
return $this->model(Models\Endpoint::class, ['id' => $id]);
}
/**
* Returns a generator which will yield a collection of endpoint objects. The elements which generators yield can be
* accessed using a foreach loop. Often the API will not return the full state of the resource in collections; you
* will need to use retrieve() to pull in the full state of the remote resource from the API.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::getEndpoints}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Endpoint>
*/
public function listEndpoints(array $options = []): \Generator
{
return $this->model(Models\Endpoint::class)->enumerate($this->api->getEndpoints(), $options);
}
/**
* Creates a new domain according to the provided options.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::postDomains}
*/
public function createDomain(array $options): Models\Domain
{
return $this->model(Models\Domain::class)->create($options);
}
/**
* Returns a generator which will yield a collection of domain objects. The elements which generators yield can be
* accessed using a foreach loop. Often the API will not return the full state of the resource in collections; you
* will need to use retrieve() to pull in the full state of the remote resource from the API.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::getDomains}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Domain>
*/
public function listDomains(array $options = []): \Generator
{
return $this->model(Models\Domain::class)->enumerate($this->api->getDomains(), $options);
}
/**
* Retrieves a domain object and populates its unique identifier object. This operation will not perform a GET or
* HEAD request by default; you will need to call retrieve() if you want to pull in remote state from the API.
*
* @param string $id The unique ID of the domain
*/
public function getDomain(string $id): Models\Domain
{
return $this->model(Models\Domain::class, ['id' => $id]);
}
/**
* Creates a new project according to the provided options.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::postProjects}
*/
public function createProject(array $options): Models\Project
{
return $this->model(Models\Project::class)->create($options);
}
/**
* Returns a generator which will yield a collection of project objects. The elements which generators yield can be
* accessed using a foreach loop. Often the API will not return the full state of the resource in collections; you
* will need to use retrieve() to pull in the full state of the remote resource from the API.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::getProjects}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Project>
*/
public function listProjects(array $options = []): \Generator
{
return $this->model(Models\Project::class)->enumerate($this->api->getProjects(), $options);
}
/**
* Retrieves a project object and populates its unique identifier object. This operation will not perform a GET or
* HEAD request by default; you will need to call retrieve() if you want to pull in remote state from the API.
*
* @param string $id The unique ID of the project
*/
public function getProject(string $id): Models\Project
{
return $this->model(Models\Project::class, ['id' => $id]);
}
/**
* Creates a new user according to the provided options.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::postUsers}
*/
public function createUser(array $options): Models\User
{
return $this->model(Models\User::class)->create($options);
}
/**
* Returns a generator which will yield a collection of user objects. The elements which generators yield can be
* accessed using a foreach loop. Often the API will not return the full state of the resource in collections; you
* will need to use retrieve() to pull in the full state of the remote resource from the API.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::getUsers}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\User>
*/
public function listUsers(array $options = []): \Generator
{
return $this->model(Models\User::class)->enumerate($this->api->getUsers(), $options);
}
/**
* Retrieves a user object and populates its unique identifier object. This operation will not perform a GET or
* HEAD request by default; you will need to call retrieve() if you want to pull in remote state from the API.
*
* @param string $id The unique ID of the user
*/
public function getUser(string $id): Models\User
{
return $this->model(Models\User::class, ['id' => $id]);
}
/**
* Creates a new group according to the provided options.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::postGroups}
*/
public function createGroup(array $options): Models\Group
{
return $this->model(Models\Group::class)->create($options);
}
/**
* Returns a generator which will yield a collection of group objects. The elements which generators yield can be
* accessed using a foreach loop. Often the API will not return the full state of the resource in collections; you
* will need to use retrieve() to pull in the full state of the remote resource from the API.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::getGroups}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Group>
*/
public function listGroups(array $options = []): \Generator
{
return $this->model(Models\Group::class)->enumerate($this->api->getGroups(), $options);
}
/**
* Retrieves a group object and populates its unique identifier object. This operation will not perform a GET or
* HEAD request by default; you will need to call retrieve() if you want to pull in remote state from the API.
*
* @param string $id The unique ID of the group
*/
public function getGroup($id): Models\Group
{
return $this->model(Models\Group::class, ['id' => $id]);
}
/**
* Creates a new credential according to the provided options.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::postCredentials}
*/
public function createCredential(array $options): Models\Credential
{
return $this->model(Models\Credential::class)->create($options);
}
/**
* Returns a generator which will yield a collection of credential objects. The elements which generators yield can
* be accessed using a foreach loop. Often the API will not return the full state of the resource in collections;
* you will need to use retrieve() to pull in the full state of the remote resource from the API.
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Credential>
*/
public function listCredentials(): \Generator
{
return $this->model(Models\Credential::class)->enumerate($this->api->getCredentials());
}
/**
* Retrieves a credential object and populates its unique identifier object. This operation will not perform a GET
* or HEAD request by default; you will need to call retrieve() if you want to pull in remote state from the API.
*
* @param string $id The unique ID of the credential
*/
public function getCredential(string $id): Models\Credential
{
return $this->model(Models\Credential::class, ['id' => $id]);
}
/**
* Creates a new role according to the provided options.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::postRoles}
*/
public function createRole(array $options): Models\Role
{
return $this->model(Models\Role::class)->create($options);
}
/**
* Returns a generator which will yield a collection of role objects. The elements which generators yield can be
* accessed using a foreach loop. Often the API will not return the full state of the resource in collections; you
* will need to use retrieve() to pull in the full state of the remote resource from the API.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::getRoles}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Role>
*/
public function listRoles(array $options = []): \Generator
{
return $this->model(Models\Role::class)->enumerate($this->api->getRoles(), $options);
}
/**
* Returns a generator which will yield a collection of role assignment objects. The elements which generators
* yield can be accessed using a foreach loop. Often the API will not return the full state of the resource in
* collections; you will need to use retrieve() to pull in the full state of the remote resource from the API.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::getRoleAssignments}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Assignment>
*/
public function listRoleAssignments(array $options = []): \Generator
{
return $this->model(Models\Assignment::class)->enumerate($this->api->getRoleAssignments(), $options);
}
/**
* Creates a new policy according to the provided options.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::postPolicies}
*/
public function createPolicy(array $options): Models\Policy
{
return $this->model(Models\Policy::class)->create($options);
}
/**
* Returns a generator which will yield a collection of policy objects. The elements which generators yield can be
* accessed using a foreach loop. Often the API will not return the full state of the resource in collections; you
* will need to use retrieve() to pull in the full state of the remote resource from the API.
*
* @param array $options {@see \OpenStack\Identity\v3\Api::getPolicies}
*
* @return \Generator<mixed, \OpenStack\Identity\v3\Models\Policy>
*/
public function listPolicies(array $options = []): \Generator
{
return $this->model(Models\Policy::class)->enumerate($this->api->getPolicies(), $options);
}
/**
* Retrieves a policy object and populates its unique identifier object. This operation will not perform a GET or
* HEAD request by default; you will need to call retrieve() if you want to pull in remote state from the API.
*
* @param string $id The unique ID of the policy
*/
public function getPolicy(string $id): Models\Policy
{
return $this->model(Models\Policy::class, ['id' => $id]);
}
}
+196
View File
@@ -0,0 +1,196 @@
<?php
declare(strict_types=1);
namespace OpenStack\Images\v2;
use OpenStack\Common\Api\AbstractApi;
class Api extends AbstractApi
{
private $basePath;
public function __construct()
{
$this->params = new Params();
$this->basePath = 'v2/';
}
public function postImages(): array
{
return [
'method' => 'POST',
'path' => $this->basePath.'images',
'params' => [
'name' => $this->params->imageName(),
'visibility' => $this->params->visibility(),
'tags' => $this->params->tags(),
'containerFormat' => $this->params->containerFormat(),
'diskFormat' => $this->params->diskFormat(),
'minDisk' => $this->params->minDisk(),
'minRam' => $this->params->minRam(),
'protected' => $this->params->protectedParam(),
],
];
}
public function getImages(): array
{
return [
'method' => 'GET',
'path' => $this->basePath.'images',
'params' => [
'limit' => $this->params->limit(),
'marker' => $this->params->marker(),
'sortKey' => $this->params->sortKey(),
'sortDir' => $this->params->sortDir(),
'name' => $this->params->queryName(),
'visibility' => $this->params->queryVisibility(),
'memberStatus' => $this->params->queryMemberStatus(),
'owner' => $this->params->queryOwner(),
'status' => $this->params->queryStatus(),
'sizeMin' => $this->params->querySizeMin(),
'sizeMax' => $this->params->querySizeMax(),
'tag' => $this->params->queryTag(),
],
];
}
public function getImage(): array
{
return [
'method' => 'GET',
'path' => $this->basePath.'images/{id}',
'params' => ['id' => $this->params->idPath()],
];
}
public function patchImage(): array
{
return [
'method' => 'PATCH',
'path' => $this->basePath.'images/{id}',
'params' => [
'id' => $this->params->idPath(),
'patchDoc' => $this->params->patchDoc(),
'contentType' => $this->params->contentType(),
],
];
}
public function deleteImage(): array
{
return [
'method' => 'DELETE',
'path' => $this->basePath.'images/{id}',
'params' => ['id' => $this->params->idPath()],
];
}
public function reactivateImage(): array
{
return [
'method' => 'POST',
'path' => $this->basePath.'images/{id}/actions/reactivate',
'params' => ['id' => $this->params->idPath()],
];
}
public function deactivateImage(): array
{
return [
'method' => 'POST',
'path' => $this->basePath.'images/{id}/actions/deactivate',
'params' => ['id' => $this->params->idPath()],
];
}
public function postImageData(): array
{
return [
'method' => 'PUT',
'path' => $this->basePath.'images/{id}/file',
'params' => [
'id' => $this->params->idPath(),
'data' => $this->params->data(),
'contentType' => $this->params->contentType(),
],
];
}
public function getImageData(): array
{
return [
'method' => 'GET',
'path' => $this->basePath.'images/{id}/file',
'params' => ['id' => $this->params->idPath()],
];
}
public function getImageSchema(): array
{
return [
'method' => 'GET',
'path' => $this->basePath.'schemas/image',
'params' => [],
];
}
public function postImageMembers(): array
{
return [
'method' => 'POST',
'path' => $this->basePath.'images/{imageId}/members',
'params' => [
'imageId' => $this->params->idPath(),
'id' => $this->params->memberId(),
],
];
}
public function getImageMembers(): array
{
return [
'method' => 'GET',
'path' => $this->basePath.'images/{imageId}/members',
'params' => ['imageId' => $this->params->idPath()],
];
}
public function getImageMember(): array
{
return [
'method' => 'GET',
'path' => $this->basePath.'images/{imageId}/members/{id}',
'params' => [
'imageId' => $this->params->idPath(),
'id' => $this->params->idPath(),
],
];
}
public function deleteImageMember(): array
{
return [
'method' => 'DELETE',
'path' => $this->basePath.'images/{imageId}/members/{id}',
'params' => [
'imageId' => $this->params->idPath(),
'id' => $this->params->idPath(),
],
];
}
public function putImageMember(): array
{
return [
'method' => 'PUT',
'path' => $this->basePath.'images/{imageId}/members/{id}',
'params' => [
'imageId' => $this->params->idPath(),
'id' => $this->params->idPath(),
'status' => $this->params->status(),
],
];
}
}
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace OpenStack\Images\v2;
class JsonPatch extends \OpenStack\Common\JsonSchema\JsonPatch
{
public function disableRestrictedPropRemovals(array $diff, array $restrictedProps): array
{
foreach ($diff as $i => $changeSet) {
if ('remove' == $changeSet['op'] && in_array($changeSet['path'], $restrictedProps)) {
unset($diff[$i]);
}
}
return $diff;
}
/**
* {@inheritdoc}
*
* We need to override the proper way to handle objects because Glance v2 does not
* support whole document replacement with empty JSON pointers.
*/
protected function handleObject(\stdClass $srcStruct, \stdClass $desStruct, string $path): array
{
$changes = [];
foreach ($desStruct as $key => $val) {
if (!property_exists($srcStruct, $key)) {
$changes[] = $this->makePatch(self::OP_ADD, $this->path($path, $key), $val);
} elseif ($srcStruct->$key != $val) {
$changes = array_merge($changes, $this->makeDiff($srcStruct->$key, $val, $this->path($path, $key)));
}
}
if ($this->shouldPartiallyReplace($desStruct, $srcStruct)) {
foreach ($srcStruct as $key => $val) {
if (!property_exists($desStruct, $key)) {
$changes[] = $this->makePatch(self::OP_REMOVE, $this->path($path, $key));
}
}
}
return $changes;
}
protected function handleArray(array $srcStruct, array $desStruct, string $path): array
{
$changes = [];
if ($srcStruct != $desStruct) {
if ($diff = $this->arrayDiff($desStruct, $srcStruct)) {
$changes[] = $this->makePatch(self::OP_REPLACE, $path, $desStruct);
}
foreach ($srcStruct as $key => $val) {
if (!in_array($val, $desStruct, true)) {
$changes[] = $this->makePatch(self::OP_REMOVE, $this->path($path, $key));
}
}
}
return $changes;
}
}
@@ -0,0 +1,225 @@
<?php
declare(strict_types=1);
namespace OpenStack\Images\v2\Models;
use OpenStack\Common\JsonSchema\Schema;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
use OpenStack\Common\Transport\Utils;
use OpenStack\Images\v2\JsonPatch;
use Psr\Http\Message\StreamInterface;
/**
* @property \OpenStack\Images\v2\Api $api
*/
class Image extends OperatorResource implements Creatable, Listable, Retrievable, Deletable
{
/** @var string */
public $status;
/** @var string */
public $name;
/** @var array */
public $tags;
/** @var string */
public $containerFormat;
/** @var \DateTimeImmutable */
public $createdAt;
/** @var string */
public $diskFormat;
/** @var \DateTimeImmutable */
public $updatedAt;
/** @var string */
public $visibility;
/** @var int */
public $minDisk;
/** @var bool */
public $protected;
/** @var string */
public $id;
/** @var \GuzzleHttp\Psr7\Uri */
public $fileUri;
/** @var string */
public $checksum;
/** @var string */
public $ownerId;
/** @var int */
public $size;
/** @var int */
public $minRam;
/** @var \GuzzleHttp\Psr7\Uri */
public $schemaUri;
/** @var int */
public $virtualSize;
private $jsonSchema;
protected $aliases = [
'container_format' => 'containerFormat',
'disk_format' => 'diskFormat',
'min_disk' => 'minDisk',
'owner' => 'ownerId',
'min_ram' => 'minRam',
'virtual_size' => 'virtualSize',
];
protected function getAliases(): array
{
return parent::getAliases() + [
'created_at' => new Alias('createdAt', \DateTimeImmutable::class),
'updated_at' => new Alias('updatedAt', \DateTimeImmutable::class),
'fileUri' => new Alias('fileUri', \GuzzleHttp\Psr7\Uri::class),
'schemaUri' => new Alias('schemaUri', \GuzzleHttp\Psr7\Uri::class),
];
}
public function populateFromArray(array $data): self
{
parent::populateFromArray($data);
$baseUri = $this->getHttpBaseUrl();
if (isset($data['file'])) {
$this->fileUri = Utils::appendPath($baseUri, $data['file']);
}
if (isset($data['schema'])) {
$this->schemaUri = Utils::appendPath($baseUri, $data['schema']);
}
return $this;
}
public function create(array $data): Creatable
{
$response = $this->execute($this->api->postImages(), $data);
return $this->populateFromResponse($response);
}
public function retrieve()
{
$response = $this->executeWithState($this->api->getImage());
$this->populateFromResponse($response);
}
private function getSchema(): Schema
{
if (null === $this->jsonSchema) {
$response = $this->execute($this->api->getImageSchema());
$this->jsonSchema = new Schema(Utils::jsonDecode($response, false));
}
return $this->jsonSchema;
}
public function update(array $data)
{
// retrieve latest state so we can accurately produce a diff
$this->retrieve();
$schema = $this->getSchema();
$data = (object) $data;
$aliasNames = array_map(
function (Alias $a) {
return $a->propertyName;
},
$this->getAliases()
);
// formulate src and des structures
$des = $schema->normalizeObject($data, $aliasNames);
$src = $schema->normalizeObject($this, $aliasNames);
// validate user input
$schema->validate($des);
if (!$schema->isValid()) {
throw new \RuntimeException($schema->getErrorString());
}
// formulate diff
$patch = new JsonPatch();
$diff = $patch->disableRestrictedPropRemovals($patch->diff($src, $des), $schema->getPropertyPaths());
$json = json_encode($diff, JSON_UNESCAPED_SLASHES);
// execute patch operation
$response = $this->execute($this->api->patchImage(), [
'id' => $this->id,
'patchDoc' => $json,
'contentType' => 'application/openstack-images-v2.1-json-patch',
]);
$this->populateFromResponse($response);
}
public function delete()
{
$this->executeWithState($this->api->deleteImage());
}
public function deactivate()
{
$this->executeWithState($this->api->deactivateImage());
}
public function reactivate()
{
$this->executeWithState($this->api->reactivateImage());
}
public function uploadData(StreamInterface $stream)
{
$this->execute($this->api->postImageData(), [
'id' => $this->id,
'data' => $stream,
'contentType' => 'application/octet-stream',
]);
}
public function downloadData(): StreamInterface
{
$response = $this->executeWithState($this->api->getImageData());
return $response->getBody();
}
public function addMember($memberId): Member
{
return $this->model(Member::class, ['imageId' => $this->id, 'id' => $memberId])->create([]);
}
/**
* @return \Generator<mixed, \OpenStack\Images\v2\Models\Member>
*/
public function listMembers(): \Generator
{
return $this->model(Member::class)->enumerate($this->api->getImageMembers(), ['imageId' => $this->id]);
}
public function getMember($memberId): Member
{
return $this->model(Member::class, ['imageId' => $this->id, 'id' => $memberId]);
}
}
@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace OpenStack\Images\v2\Models;
use OpenStack\Common\Resource\Alias;
use OpenStack\Common\Resource\Creatable;
use OpenStack\Common\Resource\Deletable;
use OpenStack\Common\Resource\Listable;
use OpenStack\Common\Resource\OperatorResource;
use OpenStack\Common\Resource\Retrievable;
/**
* @property \OpenStack\Images\v2\Api $api
*/
class Member extends OperatorResource implements Creatable, Listable, Retrievable, Deletable
{
public const STATUS_ACCEPTED = 'accepted';
public const STATUS_PENDING = 'pending';
public const STATUS_REJECTED = 'rejected';
/** @var string */
public $imageId;
/** @var string */
public $id;
/** @var \DateTimeImmutable */
public $createdAt;
/** @var \DateTimeImmutable */
public $updatedAt;
/** @var string */
public $schemaUri;
/** @var string */
public $status;
protected $aliases = [
'member_id' => 'id',
'image_id' => 'imageId',
];
protected function getAliases(): array
{
return parent::getAliases() + [
'created_at' => new Alias('createdAt', \DateTimeImmutable::class),
'updated_at' => new Alias('updatedAt', \DateTimeImmutable::class),
];
}
public function create(array $userOptions): Creatable
{
$response = $this->executeWithState($this->api->postImageMembers());
return $this->populateFromResponse($response);
}
public function retrieve()
{
$response = $this->executeWithState($this->api->getImageMember());
$this->populateFromResponse($response);
}
public function delete()
{
$this->executeWithState($this->api->deleteImageMember());
}
public function updateStatus($status)
{
$this->status = $status;
$this->executeWithState($this->api->putImageMember());
}
}
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace OpenStack\Images\v2\Models;
use JsonSchema\Validator;
class Schema extends \OpenStack\Common\JsonSchema\Schema
{
public function __construct($data, ?Validator $validator = null)
{
if (!isset($data->type)) {
$data->type = 'object';
}
foreach ($data->properties as $propertyName => &$property) {
if (false !== strpos($property->description, 'READ-ONLY')) {
$property->readOnly = true;
}
}
parent::__construct($data, $validator);
}
}
@@ -0,0 +1,214 @@
<?php
declare(strict_types=1);
namespace OpenStack\Images\v2;
use OpenStack\Common\Api\AbstractParams;
use Psr\Http\Message\StreamInterface;
class Params extends AbstractParams
{
public function imageName(): array
{
return array_merge($this->name('image'), [
'description' => 'Name for the image. The name of an image is not unique to an Image service node. The '.
'API cannot expect users to know the names of images owned by others.',
'required' => true,
]);
}
public function visibility(): array
{
return [
'location' => self::JSON,
'type' => self::STRING_TYPE,
'description' => 'Image visibility. Public or private. Default is public.',
'enum' => ['private', 'public', 'community', 'shared'],
];
}
public function tags(): array
{
return [
'location' => self::JSON,
'type' => self::ARRAY_TYPE,
'description' => 'Image tags',
'items' => ['type' => self::STRING_TYPE],
];
}
public function containerFormat(): array
{
return [
'location' => self::JSON,
'type' => self::STRING_TYPE,
'sentAs' => 'container_format',
'description' => 'Format of the container. A valid value is ami, ari, aki, bare, ovf, or ova.',
'enum' => ['ami', 'ari', 'aki', 'bare', 'ovf', 'ova'],
];
}
public function diskFormat(): array
{
return [
'location' => self::JSON,
'type' => self::STRING_TYPE,
'sentAs' => 'disk_format',
'description' => 'Format of the container. A valid value is ami, ari, aki, bare, ovf, or ova.',
'enum' => ['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi', 'iso'],
];
}
public function minDisk(): array
{
return [
'location' => self::JSON,
'type' => self::INT_TYPE,
'sentAs' => 'min_disk',
'description' => 'Amount of disk space in GB that is required to boot the image.',
];
}
public function minRam(): array
{
return [
'location' => self::JSON,
'type' => self::INT_TYPE,
'sentAs' => 'min_ram',
'description' => 'Amount of RAM in GB that is required to boot the image.',
];
}
public function protectedParam(): array
{
return [
'location' => self::JSON,
'type' => self::BOOL_TYPE,
'description' => 'If true, image is not deletable.',
];
}
public function queryName(): array
{
return [
'location' => self::QUERY,
'type' => self::STRING_TYPE,
'description' => 'Shows only images with this name. A valid value is the name of the image as a string.',
];
}
public function queryVisibility(): array
{
return [
'location' => self::QUERY,
'type' => self::STRING_TYPE,
'description' => 'Shows only images with this image visibility value or values.',
'enum' => ['public', 'private', 'shared'],
];
}
public function queryMemberStatus(): array
{
return [
'location' => self::QUERY,
'type' => self::STRING_TYPE,
'description' => 'Shows only images with this member status.',
'enum' => ['accepted', 'pending', 'rejected', 'all'],
];
}
public function queryOwner(): array
{
return [
'location' => self::QUERY,
'type' => self::STRING_TYPE,
'description' => 'Shows only images that are shared with this owner.',
];
}
public function queryStatus(): array
{
return [
'location' => self::QUERY,
'type' => self::STRING_TYPE,
'description' => 'Shows only images with this image status.',
'enum' => ['queued', 'saving', 'active', 'killed', 'deleted', 'pending_delete'],
];
}
public function querySizeMin(): array
{
return [
'location' => self::QUERY,
'type' => self::INT_TYPE,
'description' => 'Shows only images with this minimum image size.',
];
}
public function querySizeMax(): array
{
return [
'location' => self::QUERY,
'type' => self::INT_TYPE,
'description' => 'Shows only images with this maximum image size.',
];
}
public function queryTag(): array
{
return [
'location' => self::QUERY,
'type' => self::STRING_TYPE,
'description' => 'Image tag.',
];
}
public function contentType(): array
{
return [
'location' => self::HEADER,
'type' => self::STRING_TYPE,
'sentAs' => 'Content-Type',
];
}
public function patchDoc(): array
{
return [
'location' => self::RAW,
'type' => self::STRING_TYPE,
'required' => true,
'documented' => false,
];
}
public function data(): array
{
return [
'location' => self::RAW,
'type' => StreamInterface::class,
'required' => true,
'documented' => false,
];
}
public function memberId(): array
{
return [
'location' => self::JSON,
'sentAs' => 'member',
'type' => self::STRING_TYPE,
'documented' => false,
];
}
public function status(): array
{
return [
'location' => self::JSON,
'type' => self::STRING_TYPE,
'enum' => ['pending', 'accepted', 'rejected'],
];
}
}
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace OpenStack\Images\v2;
use OpenStack\Common\Service\AbstractService;
use OpenStack\Images\v2\Models\Image;
/**
* @property Api $api
*/
class Service extends AbstractService
{
public function createImage(array $data): Image
{
return $this->model(Image::class)->create($data);
}
/**
* @return \Generator<mixed, \OpenStack\Images\v2\Models\Image>
*/
public function listImages(array $data = []): \Generator
{
return $this->model(Image::class)->enumerate($this->api->getImages(), $data);
}
/**
* @param null $id
*/
public function getImage($id = null): Image
{
$image = $this->model(Image::class);
$image->populateFromArray(['id' => $id]);
return $image;
}
}
@@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
namespace OpenStack\Metric\v1\Gnocchi;
use OpenStack\Common\Api\AbstractApi;
class Api extends AbstractApi
{
private $pathPrefix = 'v1';
public function __construct()
{
$this->params = new Params();
}
public function getResources(): array
{
return [
'path' => $this->pathPrefix.'/resource/{type}',
'method' => 'GET',
'params' => [
'limit' => $this->params->limit(),
'marker' => $this->params->marker(),
'sort' => $this->params->sort(),
'type' => $this->params->resourceType(),
],
];
}
public function getResource(): array
{
return [
'path' => $this->pathPrefix.'/resource/{type}/{id}',
'method' => 'GET',
'params' => [
'id' => $this->params->idUrl('resource'),
'type' => $this->params->resourceType(),
],
];
}
public function searchResources(): array
{
return [
'path' => $this->pathPrefix.'/search/resource/{type}',
'method' => 'POST',
'params' => [
'limit' => $this->params->limit(),
'marker' => $this->params->marker(),
'sort' => $this->params->sort(),
'type' => $this->params->resourceType(),
'criteria' => $this->params->criteria(),
'contentType' => $this->params->headerContentType(),
],
];
}
public function getResourceTypes(): array
{
return [
'path' => $this->pathPrefix.'/resource_type',
'method' => 'GET',
'params' => [],
];
}
public function getMetric(): array
{
return [
'path' => $this->pathPrefix.'/metric/{id}',
'method' => 'GET',
'params' => [
'id' => $this->params->idUrl('metric'),
],
];
}
public function getMetrics(): array
{
return [
'path' => $this->pathPrefix.'/metric',
'method' => 'GET',
'params' => [
'limit' => $this->params->limit(),
'marker' => $this->params->marker(),
'sort' => $this->params->sort(),
],
];
}
public function getResourceMetrics(): array
{
return [
'path' => $this->pathPrefix.'/resource/generic/{resourceId}/metric',
'method' => 'GET',
'params' => [
'resourceId' => $this->params->idUrl('metric'),
],
];
}
public function getResourceMetric(): array
{
return [
'path' => $this->pathPrefix.'/resource/{type}/{resourceId}/metric/{metric}',
'method' => 'GET',
'params' => [
'resourceId' => $this->params->idUrl('resource'),
'metric' => $this->params->idUrl('metric'),
'type' => $this->params->resourceType(),
],
];
}
public function getResourceMetricMeasures(): array
{
return [
'path' => $this->pathPrefix.'/resource/{type}/{resourceId}/metric/{metric}/measures',
'method' => 'GET',
'params' => [
'resourceId' => $this->params->idUrl('resource'),
'metric' => $this->params->idUrl('metric'),
'type' => $this->params->resourceType(),
'granularity' => $this->params->granularity(),
'aggregation' => $this->params->aggregation(),
'start' => $this->params->measureStart(),
'stop' => $this->params->measureStop(),
],
];
}
}

Some files were not shown because too many files have changed in this diff Show More