diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index aaee5f85..1e3abeb1 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: [ '8.1', '8.2', '8.3', '8.4'] + php-versions: [ '8.2', '8.3', '8.4', '8.5'] steps: - uses: actions/checkout@v2 - name: Setup PHP diff --git a/CHANGELOG.md b/CHANGELOG.md index ebe53641..911431c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 3.0.0-alpha.1 (18.03.2026) + +### Breaking Changes +* Minimum PHP version is now 8.2. +* Removed dependency `illuminate/collections` - Replaced all `collect()` usages with native PHP array functions. +* Method `delete()` now returns `?APIResponse` instead of `bool` across all models. +* Changed various method signatures to use native PHP types and improved type hinting. + +### Features +* Improved support for Managed Certificates. +* Improved support for Firewall label selectors. +* Improved support for Primary IPs. +* Improved support for Placement Groups. +* Improved support for Load Balancers. + +### Internal +* General code cleanup and modernization. + ## 2.5.0 (12.10.2021) * Upgrade to GitHub-native Dependabot by @dependabot-preview in https://github.com/LKDevelopment/hetzner-cloud-php-sdk/pull/73 diff --git a/README.md b/README.md index dcb9174c..ae4f1270 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,11 @@ You can just run `phpunit`. The whole library is based on unit tests and sample ### Changelog -Please see [CHANGELOG](https://github.com/LKDevelopment/hetzner-cloud-php-sdk/releases) for more information what has changed recently. +Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. + +### Upgrade Guide + +Please see [Upgrade to v3.0](UPGRADE-3.0.md) for more information how to upgrade from v2.x. ### Security diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md new file mode 100644 index 00000000..904501af --- /dev/null +++ b/UPGRADE-3.0.md @@ -0,0 +1,76 @@ +# Upgrade Guide: v2.x to v3.0 + +This guide provides instructions for upgrading your application from `hetzner-cloud-php-sdk` version 2.x to 3.0. + +## Breaking Changes + +### PHP Version Requirement +- **Requirement:** PHP 8.2 or higher is now required. +- **Action:** Ensure your server or environment is running PHP 8.2+. + +### Removal of `illuminate/collections` +The dependency on `illuminate/collections` has been removed to reduce the library's footprint and avoid dependency conflicts. All internal usages of `collect()` have been replaced with native PHP array functions. + +- **Impact:** If your application relied on the SDK returning Laravel Collections, this will no longer happen. +- **Action:** Use native PHP array functions like `array_map()`, `array_filter()`, or wrap the returned arrays in a collection yourself if you still need them. + +**Before (v2.x):** +```php +// Some internal methods or if you were using collect() on SDK results +$names = collect($hetznerClient->servers()->all())->map(fn($s) => $s->name); +``` + +**After (v3.0):** +```php +// Results are now always native arrays +$servers = $hetznerClient->servers()->all(); +$names = array_map(fn($s) => $s->name, $servers); + +// If you want to keep using Collections in your project: +// composer require illuminate/collections +$names = collect($hetznerClient->servers()->all())->map(fn($s) => $s->name); +``` + +### `delete()` Method Return Type Change +The `delete()` method on all resource models now returns an `APIResponse` object (or `null`) instead of a `boolean`. This provides more information about the API response, such as the `Action` created by the deletion. + +- **Impact:** Any code checking for `if ($model->delete())` will still work as `APIResponse` is truthy, but it is better to update your logic if you were relying on the boolean result. + +**Before (v2.x):** +```php +$success = $server->delete(); // returned bool +if ($success) { + // ... +} +``` + +**After (v3.0):** +```php +$response = $server->delete(); // returns ?APIResponse +if ($response !== null) { + $action = $response->action; // You can now access the action + // ... +} +``` + +### Native Type Hints +Many method signatures have been updated to include native PHP 8.1 type hints. This improves IDE support and static analysis. + +- **Impact:** If you have extended SDK classes and overridden methods, you may need to update your method signatures to match the new type hints to avoid PHP fatal errors. + +--- + +## New Features +Version 3.0 also introduces several new features and improvements: +- Support for **Primary IPs**. +- Support for **Placement Groups**. +- Support for **Firewalls** (including label selectors). +- Improved support for **Load Balancers**. +- Improved support for **Managed Certificates**. + +## Summary for LLMs +- Minimum PHP: `8.2` +- Dependency removed: `illuminate/collections` +- `delete()` returns: `?LKDev\HetznerCloud\APIResponse` (was `bool`) +- All list methods return: `array` +- Models use native type hints for parameters and return types. diff --git a/composer.json b/composer.json index f1875925..747c0794 100644 --- a/composer.json +++ b/composer.json @@ -8,11 +8,10 @@ "hetzner cloud", "cloud php library" ], - "require": { - "php": "^8.1", + "require": { + "php": "^8.2", "ext-json": "*", - "guzzlehttp/guzzle": "^6.3|^7.0", - "illuminate/collections": "^5.5|^v6.18|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0" + "guzzlehttp/guzzle": "^6.3|^7.0" }, "require-dev": { "phpunit/phpunit": "^7.0|^8.5.5|^9.0", diff --git a/src/HetznerAPIClient.php b/src/HetznerAPIClient.php index 11667539..0998f44b 100644 --- a/src/HetznerAPIClient.php +++ b/src/HetznerAPIClient.php @@ -32,7 +32,7 @@ class HetznerAPIClient /** * Version of the API Client. */ - const VERSION = '2.9.1'; + const VERSION = '3.0.0'; const MAX_ENTITIES_PER_PAGE = 50; diff --git a/src/Models/Actions/Action.php b/src/Models/Actions/Action.php index 50ea59da..9c50010c 100644 --- a/src/Models/Actions/Action.php +++ b/src/Models/Actions/Action.php @@ -28,12 +28,12 @@ class Action extends Model implements Resource public $progress; /** - * @var string + * @var string|null */ public $started; /** - * @var string + * @var string|null */ public $finished; @@ -66,7 +66,7 @@ public function __construct( string $command, int $progress, string $status, - string $started, + ?string $started = null, ?string $finished = null, $resources = null, $error = null @@ -148,6 +148,6 @@ public static function parse($input) return; } - return new self($input->id, $input->command, $input->progress, $input->status, $input->started, $input->finished, $input->resources, $input->error ?? null); + return new self($input->id, $input->command, $input->progress, $input->status, $input->started ?? null, $input->finished ?? null, $input->resources, $input->error ?? null); } } diff --git a/src/Models/Actions/Actions.php b/src/Models/Actions/Actions.php index 666f19a3..3454d36b 100644 --- a/src/Models/Actions/Actions.php +++ b/src/Models/Actions/Actions.php @@ -80,9 +80,9 @@ public function getByName(string $name) */ public function setAdditionalData($input) { - $this->actions = collect($input)->map(function ($action, $key) { + $this->actions = array_map(function ($action) { return Action::parse($action); - })->toArray(); + }, $input); return $this; } diff --git a/src/Models/Certificates/Certificate.php b/src/Models/Certificates/Certificate.php index 10a57063..403d6689 100644 --- a/src/Models/Certificates/Certificate.php +++ b/src/Models/Certificates/Certificate.php @@ -57,6 +57,10 @@ class Certificate extends Model implements Resource * @var array */ public $labels; + /** + * @var string + */ + public $type; /** * Certificate constructor. @@ -71,8 +75,9 @@ class Certificate extends Model implements Resource * @param string|null $fingerprint * @param array|null $used_by * @param array|null $labels + * @param string|null $type */ - public function __construct(int $id, ?string $name = null, ?string $certificate = null, ?string $created = null, ?string $not_valid_before = null, ?string $not_valid_after = null, ?array $domain_names = null, ?string $fingerprint = null, $used_by = null, $labels = []) + public function __construct(int $id, ?string $name = null, ?string $certificate = null, ?string $created = null, ?string $not_valid_before = null, ?string $not_valid_after = null, ?array $domain_names = null, ?string $fingerprint = null, $used_by = null, $labels = [], ?string $type = null) { $this->id = $id; $this->name = $name; @@ -84,6 +89,7 @@ public function __construct(int $id, ?string $name = null, ?string $certificate $this->fingerprint = $fingerprint; $this->used_by = $used_by; $this->labels = $labels; + $this->type = $type; parent::__construct(); } @@ -136,7 +142,7 @@ public function delete(): bool */ public static function parse($input) { - return new self($input->id, $input->name, $input->certificate, $input->created, $input->not_valid_before, $input->not_valid_after, $input->domain_names, $input->fingerprint, $input->used_by, $input->labels); + return new self($input->id, $input->name, $input->certificate, $input->created, $input->not_valid_before, $input->not_valid_after, $input->domain_names, $input->fingerprint, $input->used_by, $input->labels, $input->type ?? null); } /** diff --git a/src/Models/Certificates/Certificates.php b/src/Models/Certificates/Certificates.php index d930ce09..59a8f699 100644 --- a/src/Models/Certificates/Certificates.php +++ b/src/Models/Certificates/Certificates.php @@ -41,15 +41,21 @@ class Certificates extends Model implements Resources */ public function create( string $name, - string $certificate, - string $privateKey, - array $labels = [] + ?string $certificate = null, + ?string $privateKey = null, + array $labels = [], + string $type = 'uploaded' ): ?Certificate { $parameters = [ 'name' => $name, - 'certificate' => $certificate, - 'private_key' => $privateKey, + 'type' => $type, ]; + if ($certificate !== null) { + $parameters['certificate'] = $certificate; + } + if ($privateKey !== null) { + $parameters['private_key'] = $privateKey; + } if (! empty($labels)) { $parameters['labels'] = $labels; } @@ -116,9 +122,9 @@ public function list(?RequestOpts $requestOpts = null): ?APIResponse */ public function setAdditionalData($input) { - $this->certificates = collect($input)->map(function ($certificate, $key) { + $this->certificates = array_map(function ($certificate) { return Certificate::parse($certificate); - })->toArray(); + }, $input); return $this; } diff --git a/src/Models/Datacenters/Datacenters.php b/src/Models/Datacenters/Datacenters.php index 89eda826..f945ed7d 100644 --- a/src/Models/Datacenters/Datacenters.php +++ b/src/Models/Datacenters/Datacenters.php @@ -120,9 +120,9 @@ public function getByName(string $name): ?Datacenter */ public function setAdditionalData($input) { - $this->datacenters = collect($input)->map(function ($datacenter, $key) { + $this->datacenters = array_map(function ($datacenter) { return Datacenter::parse($datacenter); - })->toArray(); + }, $input); return $this; } diff --git a/src/Models/Firewalls/Firewall.php b/src/Models/Firewalls/Firewall.php index b6146b6c..800d981c 100644 --- a/src/Models/Firewalls/Firewall.php +++ b/src/Models/Firewalls/Firewall.php @@ -119,12 +119,14 @@ public static function parse($input): ?self $rules = []; foreach ($input->rules as $r) { - $rules[] = new FirewallRule($r->direction, $r->protocol, $r->source_ips, $r->destination_ips, (string) $r->port); + $rules[] = new FirewallRule($r->direction, $r->protocol, (isset($r->source_ips)) ? $r->source_ips : [], (isset($r->destination_ips)) ? $r->destination_ips : [], (isset($r->port)) ? (string) $r->port : '', (isset($r->description)) ? $r->description : ''); } foreach ($input->applied_to as $a) { if ($a->type === 'server') { $appliedTo[] = new FirewallResource($a->type, new Server($a->server->id)); + } elseif ($a->type === 'label_selector') { + $appliedTo[] = new FirewallResource($a->type, null, get_object_vars($a->label_selector)); } } @@ -145,18 +147,18 @@ public function setRules(array $rules): ?ApiResponse { $response = $this->httpClient->post('firewalls/'.$this->id.'/actions/set_rules', [ 'json' => [ - 'rules' => collect($rules)->map(function ($r) { + 'rules' => array_map(function ($r) { return $r->toRequestSchema(); - }), + }, $rules), ], ]); if (! HetznerAPIClient::hasError($response)) { $payload = json_decode((string) $response->getBody()); return APIResponse::create([ - 'actions' => collect($payload->actions)->map(function ($action) { + 'actions' => array_map(function ($action) { return Action::parse($action); - })->toArray(), + }, $payload->actions), ], $response->getHeaders()); } @@ -168,18 +170,18 @@ public function setRules(array $rules): ?ApiResponse * * @see https://docs.hetzner.cloud/#firewalls-delete-a-firewall * - * @return bool + * @return APIResponse|null * * @throws \LKDev\HetznerCloud\APIException */ - public function delete(): bool + public function delete(): ?APIResponse { $response = $this->httpClient->delete('firewalls/'.$this->id); if (! HetznerAPIClient::hasError($response)) { - return true; + return APIResponse::create([], $response->getHeaders()); } - return false; + return null; } /** @@ -196,18 +198,18 @@ public function applyToResources(array $resources): ?APIResponse { $response = $this->httpClient->post('firewalls/'.$this->id.'/actions/apply_to_resources', [ 'json' => [ - 'apply_to' => collect($resources)->map(function ($r) { + 'apply_to' => array_map(function ($r) { return $r->toRequestSchema(); - }), + }, $resources), ], ]); if (! HetznerAPIClient::hasError($response)) { $payload = json_decode((string) $response->getBody()); return APIResponse::create([ - 'actions' => collect($payload->actions)->map(function ($action) { + 'actions' => array_map(function ($action) { return Action::parse($action); - })->toArray(), + }, $payload->actions), ], $response->getHeaders()); } @@ -228,18 +230,18 @@ public function removeFromResources(array $resources): ?APIResponse { $response = $this->httpClient->post('firewalls/'.$this->id.'/actions/remove_from_resources', [ 'json' => [ - 'remove_from' => collect($resources)->map(function ($r) { + 'remove_from' => array_map(function ($r) { return $r->toRequestSchema(); - }), + }, $resources), ], ]); if (! HetznerAPIClient::hasError($response)) { $payload = json_decode((string) $response->getBody()); return APIResponse::create([ - 'actions' => collect($payload->actions)->map(function ($action) { + 'actions' => array_map(function ($action) { return Action::parse($action); - })->toArray(), + }, $payload->actions), ], $response->getHeaders()); } diff --git a/src/Models/Firewalls/FirewallResource.php b/src/Models/Firewalls/FirewallResource.php index ea27848e..0c8aec95 100644 --- a/src/Models/Firewalls/FirewallResource.php +++ b/src/Models/Firewalls/FirewallResource.php @@ -11,6 +11,8 @@ class FirewallResource { const TYPE_SERVER = 'server'; + const TYPE_LABEL_SELECTOR = 'label_selector'; + /** * @var string */ @@ -20,26 +22,35 @@ class FirewallResource */ public $server; + /** + * @var ?array + */ + public $labelSelector; + /** * FirewallResource constructor. * * @param string $type * @param Server|null $server + * @param array|null $labelSelector */ - public function __construct(string $type, ?Server $server) + public function __construct(string $type, ?Server $server = null, ?array $labelSelector = null) { $this->type = $type; $this->server = $server; + $this->labelSelector = $labelSelector; } /** - * @return string[] + * @return array */ public function toRequestSchema(): array { $s = ['type' => $this->type]; if ($this->type == self::TYPE_SERVER) { $s['server'] = ['id' => $this->server->id]; + } elseif ($this->type == self::TYPE_LABEL_SELECTOR) { + $s['label_selector'] = $this->labelSelector; } return $s; diff --git a/src/Models/Firewalls/FirewallRule.php b/src/Models/Firewalls/FirewallRule.php index c6da80bd..149d9f8d 100644 --- a/src/Models/Firewalls/FirewallRule.php +++ b/src/Models/Firewalls/FirewallRule.php @@ -37,6 +37,11 @@ class FirewallRule */ public $port; + /** + * @var string + */ + public $description; + /** * FirewallRule constructor. * @@ -45,14 +50,16 @@ class FirewallRule * @param string[] $destinationIPs * @param string $protocol * @param string $port + * @param string $description */ - public function __construct(string $direction, string $protocol, array $sourceIPs = [], array $destinationIPs = [], ?string $port = '') + public function __construct(string $direction, string $protocol, array $sourceIPs = [], array $destinationIPs = [], ?string $port = '', ?string $description = '') { $this->direction = $direction; $this->sourceIPs = $sourceIPs; $this->destinationIPs = $destinationIPs; $this->protocol = $protocol; $this->port = $port; + $this->description = $description; } /** @@ -71,6 +78,9 @@ public function toRequestSchema(): array if ($this->port != '') { $s['port'] = $this->port; } + if ($this->description != '') { + $s['description'] = $this->description; + } return $s; } diff --git a/src/Models/Firewalls/Firewalls.php b/src/Models/Firewalls/Firewalls.php index dd0afd24..c4846f45 100644 --- a/src/Models/Firewalls/Firewalls.php +++ b/src/Models/Firewalls/Firewalls.php @@ -134,9 +134,9 @@ public static function parse($input) */ public function setAdditionalData($input) { - $this->firewalls = collect($input)->map(function ($firewall, $key) { + $this->firewalls = array_map(function ($firewall) { return Firewall::parse($firewall); - })->toArray(); + }, $input); return $this; } @@ -163,15 +163,15 @@ public function create( 'name' => $name, ]; if (! empty($rules)) { - $parameters['rules'] = collect($rules)->map(function ($r) { + $parameters['rules'] = array_map(function ($r) { return $r->toRequestSchema(); - }); + }, $rules); } if (! empty($applyTo)) { - $parameters['apply_to'] = collect($applyTo)->map(function ($r) { + $parameters['apply_to'] = array_map(function ($r) { return $r->toRequestSchema(); - }); + }, $applyTo); } if (! empty($labels)) { $parameters['labels'] = $labels; @@ -184,9 +184,9 @@ public function create( return APIResponse::create([ 'firewall' => Firewall::parse($payload->{$this->_getKeys()['one']}), - 'actions' => collect($payload->actions)->map(function ($action) { + 'actions' => array_map(function ($action) { return Action::parse($action); - })->toArray(), + }, $payload->actions), ], $response->getHeaders()); } diff --git a/src/Models/FloatingIps/FloatingIp.php b/src/Models/FloatingIps/FloatingIp.php index ef1bc560..47833bcd 100644 --- a/src/Models/FloatingIps/FloatingIp.php +++ b/src/Models/FloatingIps/FloatingIp.php @@ -181,18 +181,18 @@ public function changeDescription(string $description): ?self * * @see https://docs.hetzner.cloud/#resources-floating-ips-delete * - * @return bool + * @return APIResponse|null * * @throws \LKDev\HetznerCloud\APIException */ - public function delete(): bool + public function delete(): ?APIResponse { $response = $this->httpClient->delete('floating_ips/'.$this->id); if (! HetznerAPIClient::hasError($response)) { - return true; + return APIResponse::create([], $response->getHeaders()); } - return false; + return null; } /** diff --git a/src/Models/FloatingIps/FloatingIps.php b/src/Models/FloatingIps/FloatingIps.php index 3b0a3255..631c6bce 100644 --- a/src/Models/FloatingIps/FloatingIps.php +++ b/src/Models/FloatingIps/FloatingIps.php @@ -123,7 +123,7 @@ public function getByName(string $name): ?FloatingIp * @param \LKDev\HetznerCloud\Models\Servers\Server|null $server * @param string|null $name * @param array $labels - * @return \LKDev\HetznerCloud\Models\FloatingIps\FloatingIp|null + * @return \LKDev\HetznerCloud\APIResponse|null * * @throws \LKDev\HetznerCloud\APIException */ @@ -134,7 +134,7 @@ public function create( ?Server $server = null, ?string $name = null, array $labels = [] - ): ?FloatingIp { + ): ?APIResponse { $parameters = [ 'type' => $type, ]; @@ -157,7 +157,12 @@ public function create( 'json' => $parameters, ]); if (! HetznerAPIClient::hasError($response)) { - return FloatingIp::parse(json_decode((string) $response->getBody())->{$this->_getKeys()['one']}); + $payload = json_decode((string) $response->getBody()); + + return APIResponse::create([ + 'floating_ip' => FloatingIp::parse($payload->floating_ip), + 'action' => property_exists($payload, 'action') ? \LKDev\HetznerCloud\Models\Actions\Action::parse($payload->action) : null, + ], $response->getHeaders()); } return null; @@ -169,9 +174,9 @@ public function create( */ public function setAdditionalData($input) { - $this->floating_ips = collect($input)->map(function ($floatingIp, $key) { + $this->floating_ips = array_map(function ($floatingIp) { return FloatingIp::parse($floatingIp); - })->toArray(); + }, $input); return $this; } diff --git a/src/Models/ISOs/ISO.php b/src/Models/ISOs/ISO.php index 6c01a03f..d353d2b2 100644 --- a/src/Models/ISOs/ISO.php +++ b/src/Models/ISOs/ISO.php @@ -28,6 +28,16 @@ class ISO extends Model implements Resource */ public $type; + /** + * @var string + */ + public $architecture; + + /** + * @var string|null + */ + public $deprecated; + /** * ISO constructor. * @@ -35,13 +45,17 @@ class ISO extends Model implements Resource * @param string $name * @param string $description * @param string $type + * @param string $architecture + * @param string|null $deprecated */ - public function __construct(int $id, ?string $name = null, ?string $description = null, ?string $type = null) + public function __construct(int $id, ?string $name = null, ?string $description = null, ?string $type = null, ?string $architecture = null, ?string $deprecated = null) { $this->id = $id; $this->name = $name; $this->description = $description; $this->type = $type; + $this->architecture = $architecture; + $this->deprecated = $deprecated; parent::__construct(); } @@ -55,7 +69,7 @@ public static function parse($input): ?self return null; } - return new self($input->id, $input->name, $input->description, $input->type); + return new self($input->id, $input->name, $input->description, $input->type, $input->architecture ?? null, $input->deprecated ?? null); } public function reload() diff --git a/src/Models/ISOs/ISOs.php b/src/Models/ISOs/ISOs.php index 3cde870c..d3be0d38 100644 --- a/src/Models/ISOs/ISOs.php +++ b/src/Models/ISOs/ISOs.php @@ -116,9 +116,9 @@ public function getByName(string $name): ?ISO */ public function setAdditionalData($input) { - $this->isos = collect($input)->map(function ($iso, $key) { + $this->isos = array_map(function ($iso) { return ISO::parse($iso); - })->toArray(); + }, $input); return $this; } diff --git a/src/Models/Images/Image.php b/src/Models/Images/Image.php index 597c3fc3..02903b54 100644 --- a/src/Models/Images/Image.php +++ b/src/Models/Images/Image.php @@ -144,6 +144,16 @@ class Image extends Model implements Resource */ public $architecture; + /** + * @var string|null + */ + public $deleted; + + /** + * @var string|null + */ + public $deprecated; + /** * Image constructor. * @@ -163,6 +173,8 @@ class Image extends Model implements Resource * @param Protection $protection * @param string $architecture * @param array $labels + * @param string|null $deleted + * @param string|null $deprecated */ public function __construct( int $id, @@ -180,7 +192,9 @@ public function __construct( ?bool $rapidDeploy = null, ?Protection $protection = null, ?string $architecture = null, - array $labels = [] + array $labels = [], + ?string $deleted = null, + ?string $deprecated = null ) { $this->id = $id; $this->type = $type; @@ -205,6 +219,8 @@ public function __construct( $this->protection = $protection; $this->architecture = $architecture; $this->labels = $labels; + $this->deleted = $deleted; + $this->deprecated = $deprecated; parent::__construct(); } @@ -285,7 +301,7 @@ public static function parse($input): ?Image return null; } - return new self($input->id, $input->type, property_exists($input, 'status') ? $input->status : null, $input->name, $input->description, $input->image_size, $input->disk_size, $input->created, $input->created_from, $input->bound_to, $input->os_flavor, $input->os_version, $input->rapid_deploy, Protection::parse($input->protection), $input->architecture, get_object_vars($input->labels)); + return new self($input->id, $input->type, $input->status, $input->name, $input->description, $input->image_size, $input->disk_size, $input->created, $input->created_from, $input->bound_to, $input->os_flavor, $input->os_version, $input->rapid_deploy, Protection::parse($input->protection), $input->architecture ?? null, get_object_vars($input->labels), $input->deleted ?? null, $input->deprecated ?? null); } public function reload() diff --git a/src/Models/Images/Images.php b/src/Models/Images/Images.php index 0183d3dc..ff9ef42a 100644 --- a/src/Models/Images/Images.php +++ b/src/Models/Images/Images.php @@ -113,9 +113,9 @@ public function getByName(string $name, ?string $architecture = null): ?Image */ public function setAdditionalData($input) { - $this->images = collect($input)->map(function ($image, $key) { + $this->images = array_map(function ($image) { return Image::parse($image); - })->toArray(); + }, $input); return $this; } diff --git a/src/Models/LoadBalancerTypes/LoadBalancerType.php b/src/Models/LoadBalancerTypes/LoadBalancerType.php index 3a5753aa..33862e21 100644 --- a/src/Models/LoadBalancerTypes/LoadBalancerType.php +++ b/src/Models/LoadBalancerTypes/LoadBalancerType.php @@ -6,6 +6,7 @@ use LKDev\HetznerCloud\Models\Contracts\Resource; use LKDev\HetznerCloud\Models\Model; use LKDev\HetznerCloud\Models\Prices\Prices; +use LKDev\HetznerCloud\Models\Prices\ServerTypePrice; class LoadBalancerType extends Model implements Resource { @@ -54,6 +55,11 @@ class LoadBalancerType extends Model implements Resource */ public $prices; + /** + * @var array + */ + public $price; + /** * @param int $id * @param string $name @@ -64,8 +70,9 @@ class LoadBalancerType extends Model implements Resource * @param int $max_services * @param int $max_targets * @param array|\LKDev\HetznerCloud\Models\Prices\Prices $prices + * @param array $price */ - public function __construct(int $id, string $name, ?string $deprecated, string $description, int $max_assigned_certificates, int $max_connections, int $max_services, int $max_targets, $prices) + public function __construct(int $id, string $name, ?string $deprecated, string $description, int $max_assigned_certificates, int $max_connections, int $max_services, int $max_targets, $prices, $price = null) { $this->id = $id; $this->name = $name; @@ -75,21 +82,29 @@ public function __construct(int $id, string $name, ?string $deprecated, string $ $this->max_connections = $max_connections; $this->max_services = $max_services; $this->max_targets = $max_targets; - $this->prices = $prices; // + $this->prices = $prices; + $this->price = $price; parent::__construct(); } - /** - * @param $input - * @return \LKDev\HetznerCloud\Models\LoadBalancerTypes\LoadBalancerType|static - */ public static function parse($input) { if ($input == null) { - return; + return null; } - return new self($input->id, $input->name, $input->deprecated, $input->description, $input->max_assigned_certificates, $input->max_connections, $input->max_services, $input->max_targets, Prices::parse($input->prices)); + return new self( + $input->id, + $input->name, + $input->deprecated ?? null, + $input->description ?? '', + $input->max_assigned_certificates ?? 0, + $input->max_connections ?? 0, + $input->max_services ?? 0, + $input->max_targets ?? 0, + Prices::parse($input->prices), + property_exists($input, 'price') ? ServerTypePrice::parse($input->price) : null + ); } public function reload() diff --git a/src/Models/LoadBalancerTypes/LoadBalancerTypes.php b/src/Models/LoadBalancerTypes/LoadBalancerTypes.php index cdb2bc08..c2d55b4b 100644 --- a/src/Models/LoadBalancerTypes/LoadBalancerTypes.php +++ b/src/Models/LoadBalancerTypes/LoadBalancerTypes.php @@ -109,9 +109,9 @@ public function getByName(string $name): ?LoadBalancerType */ public function setAdditionalData($input) { - $this->load_balancer_types = collect($input)->map(function ($loadBalancerType, $key) { + $this->load_balancer_types = array_map(function ($loadBalancerType) { return LoadBalancerType::parse($loadBalancerType); - })->toArray(); + }, $input); return $this; } diff --git a/src/Models/LoadBalancers/LoadBalancer.php b/src/Models/LoadBalancers/LoadBalancer.php index b893004a..0a2a24fc 100644 --- a/src/Models/LoadBalancers/LoadBalancer.php +++ b/src/Models/LoadBalancers/LoadBalancer.php @@ -146,14 +146,21 @@ public function reload() return HetznerAPIClient::$instance->loadBalancers()->get($this->id); } - public function delete() + /** + * @see https://docs.hetzner.cloud/#load-balancers-delete-a-load-balancer + * + * @return APIResponse|null + * + * @throws \LKDev\HetznerCloud\APIException + */ + public function delete(): ?APIResponse { $response = $this->httpClient->delete('load_balancers/'.$this->id); if (! HetznerAPIClient::hasError($response)) { - return true; + return APIResponse::create([], $response->getHeaders()); } - return false; + return null; } public function update(array $data) diff --git a/src/Models/LoadBalancers/LoadBalancerServiceHttp.php b/src/Models/LoadBalancers/LoadBalancerServiceHttp.php index 4a63967d..209d5107 100644 --- a/src/Models/LoadBalancers/LoadBalancerServiceHttp.php +++ b/src/Models/LoadBalancers/LoadBalancerServiceHttp.php @@ -58,6 +58,6 @@ public static function parse($input) return; } - return new self($input->certificates, $input->cookie_lifetime, $input->cookie_name, $input->redirect_http, $input->sticky_essions); + return new self($input->certificates, $input->cookie_lifetime, $input->cookie_name, $input->redirect_http, $input->sticky_sessions); } } diff --git a/src/Models/LoadBalancers/LoadBalancers.php b/src/Models/LoadBalancers/LoadBalancers.php index 208eef5e..afffad21 100644 --- a/src/Models/LoadBalancers/LoadBalancers.php +++ b/src/Models/LoadBalancers/LoadBalancers.php @@ -109,9 +109,9 @@ public function getByName(string $name): ?LoadBalancer */ public function setAdditionalData($input) { - $this->load_balancers = collect($input)->map(function ($loadBalancer, $key) { + $this->load_balancers = array_map(function ($loadBalancer) { return LoadBalancer::parse($loadBalancer); - })->toArray(); + }, $input); return $this; } diff --git a/src/Models/Locations/Locations.php b/src/Models/Locations/Locations.php index cdab9610..cb8099a9 100644 --- a/src/Models/Locations/Locations.php +++ b/src/Models/Locations/Locations.php @@ -116,9 +116,9 @@ public function getByName(string $name): ?Location */ public function setAdditionalData($input) { - $this->locations = collect($input)->map(function ($location, $key) { + $this->locations = array_map(function ($location) { return Location::parse($location); - })->toArray(); + }, $input); return $this; } diff --git a/src/Models/Model.php b/src/Models/Model.php index 4d6ed8d6..77ad4794 100644 --- a/src/Models/Model.php +++ b/src/Models/Model.php @@ -20,7 +20,7 @@ abstract class Model */ public function __construct(?GuzzleClient $httpClient = null) { - $this->httpClient = $httpClient == null ? HetznerAPIClient::$instance->getHttpClient() : $httpClient; + $this->httpClient = $httpClient == null ? (HetznerAPIClient::$instance ? HetznerAPIClient::$instance->getHttpClient() : null) : $httpClient; } /** diff --git a/src/Models/Networks/Network.php b/src/Models/Networks/Network.php index 3fc24fd3..b10c8634 100644 --- a/src/Models/Networks/Network.php +++ b/src/Models/Networks/Network.php @@ -67,6 +67,11 @@ class Network extends Model implements Resource */ public $created; + /** + * @var bool + */ + public $expose_routes_to_vswitch; + /** * Network constructor. * @@ -226,14 +231,14 @@ private function setAdditionalData($data) $this->ipRange = $data->ip_range; $this->subnets = Subnet::parse($data->subnets, $this->httpClient); $this->routes = Route::parse($data->routes, $this->httpClient); - $this->servers = collect($data->servers) - ->map(function ($id) { - return new Server($id); - })->toArray(); + $this->servers = array_map(function ($id) { + return new Server($id); + }, $data->servers); $this->protection = Protection::parse($data->protection); $this->labels = get_object_vars($data->labels); $this->created = $data->created; + $this->expose_routes_to_vswitch = $data->expose_routes_to_vswitch ?? false; return $this; } @@ -269,7 +274,7 @@ public function update(array $data) ]); if (! HetznerAPIClient::hasError($response)) { return APIResponse::create([ - 'network' => Server::parse(json_decode((string) $response->getBody())->network), + 'network' => self::parse(json_decode((string) $response->getBody())->network), ], $response->getHeaders()); } } diff --git a/src/Models/Networks/Networks.php b/src/Models/Networks/Networks.php index e96c426f..dfb45e4e 100644 --- a/src/Models/Networks/Networks.php +++ b/src/Models/Networks/Networks.php @@ -112,13 +112,11 @@ public function getByName(string $name): ?Network */ public function setAdditionalData($input) { - $this->networks = collect($input) - ->map(function ($network) { - if ($network != null) { - return Network::parse($network); - } - }) - ->toArray(); + $this->networks = array_map(function ($network) { + if ($network != null) { + return Network::parse($network); + } + }, $input); return $this; } @@ -129,22 +127,24 @@ public function setAdditionalData($input) * @param array $subnets * @param array $routes * @param array $labels + * @param bool $exposeRoutesToVswitch */ - public function create(string $name, string $ipRange, array $subnets = [], array $routes = [], array $labels = []) + public function create(string $name, string $ipRange, array $subnets = [], array $routes = [], array $labels = [], bool $exposeRoutesToVswitch = false) { $payload = [ 'name' => $name, 'ip_range' => $ipRange, + 'expose_routes_to_vswitch' => $exposeRoutesToVswitch, ]; if (! empty($subnets)) { - $payload['subnets'] = collect($subnets)->map(function (Subnet $s) { + $payload['subnets'] = array_map(function (Subnet $s) { return $s->__toRequestPayload(); - })->toArray(); + }, $subnets); } if (! empty($routes)) { - $payload['routes'] = collect($routes)->map(function (Route $r) { + $payload['routes'] = array_map(function (Route $r) { return $r->__toRequestPayload(); - })->toArray(); + }, $routes); } if (! empty($labels)) { $payload['labels'] = $labels; diff --git a/src/Models/Networks/Route.php b/src/Models/Networks/Route.php index 99237702..96dcf498 100644 --- a/src/Models/Networks/Route.php +++ b/src/Models/Networks/Route.php @@ -40,9 +40,9 @@ public function __construct(string $destination, string $gateway, ?GuzzleClient */ public static function parse($input, ?GuzzleClient $client = null) { - return collect($input)->map(function ($route) use ($client) { + return array_map(function ($route) use ($client) { return new self($route->destination, $route->gateway, $client); - })->toArray(); + }, $input); } /** diff --git a/src/Models/Networks/Subnet.php b/src/Models/Networks/Subnet.php index 606c0ca1..87f66b97 100644 --- a/src/Models/Networks/Subnet.php +++ b/src/Models/Networks/Subnet.php @@ -54,9 +54,9 @@ public function __construct(string $type, string $ipRange, string $networkZone, */ public static function parse($input, ?GuzzleClient $client = null) { - return collect($input)->map(function ($subnet) use ($client) { + return array_map(function ($subnet) use ($client) { return new self($subnet->type, $subnet->ip_range, $subnet->network_zone, $subnet->gateway, $client); - })->toArray(); + }, $input); } /** diff --git a/src/Models/PlacementGroups/PlacementGroup.php b/src/Models/PlacementGroups/PlacementGroup.php index 566418ef..922086b1 100644 --- a/src/Models/PlacementGroups/PlacementGroup.php +++ b/src/Models/PlacementGroups/PlacementGroup.php @@ -65,10 +65,9 @@ private function setAdditionalData($data) { $this->name = $data->name; $this->type = $data->type; - $this->servers = collect($data->servers) - ->map(function ($id) { - return new Server($id); - })->toArray(); + $this->servers = array_map(function ($id) { + return new Server($id); + }, $data->servers); $this->labels = get_object_vars($data->labels); $this->created = $data->created; @@ -107,7 +106,7 @@ public function update(array $data) ]); if (! HetznerAPIClient::hasError($response)) { return APIResponse::create([ - 'placement_group' => self::parse(json_decode((string) $response->getBody())->network), + 'placement_group' => self::parse(json_decode((string) $response->getBody())->placement_group), ], $response->getHeaders()); } diff --git a/src/Models/PlacementGroups/PlacementGroups.php b/src/Models/PlacementGroups/PlacementGroups.php index 44fb7913..e52983e5 100644 --- a/src/Models/PlacementGroups/PlacementGroups.php +++ b/src/Models/PlacementGroups/PlacementGroups.php @@ -109,13 +109,11 @@ public function getByName(string $name): ?PlacementGroup */ public function setAdditionalData($input) { - $this->placement_groups = collect($input) - ->map(function ($placementGroup) { - if ($placementGroup != null) { - return PlacementGroup::parse($placementGroup); - } - }) - ->toArray(); + $this->placement_groups = array_map(function ($placementGroup) { + if ($placementGroup != null) { + return PlacementGroup::parse($placementGroup); + } + }, $input); return $this; } diff --git a/src/Models/Prices/Price.php b/src/Models/Prices/Price.php new file mode 100644 index 00000000..b370e6d2 --- /dev/null +++ b/src/Models/Prices/Price.php @@ -0,0 +1,41 @@ +net = $net; + $this->gross = $gross; + } + + /** + * @param $input + * @return self|null + */ + public static function parse($input): ?self + { + if ($input == null) { + return null; + } + + return new self($input->net ?? '0', $input->gross ?? '0'); + } +} diff --git a/src/Models/Prices/Prices.php b/src/Models/Prices/Prices.php index 328578fb..201e3913 100644 --- a/src/Models/Prices/Prices.php +++ b/src/Models/Prices/Prices.php @@ -10,7 +10,9 @@ namespace LKDev\HetznerCloud\Models\Prices; use LKDev\HetznerCloud\HetznerAPIClient; +use LKDev\HetznerCloud\Models\LoadBalancerTypes\LoadBalancerType; use LKDev\HetznerCloud\Models\Model; +use LKDev\HetznerCloud\Models\Servers\Types\ServerType; use LKDev\HetznerCloud\RequestOpts; /** @@ -19,32 +21,110 @@ class Prices extends Model { /** - * @var \stdClass + * @var string */ - public $prices; + public $currency; + + /** + * @var string + */ + public $vat_rate; + + /** + * @var Price + */ + public $image; + + /** + * @var Price + */ + public $floating_ip; + + /** + * @var Price + */ + public $traffic; + + /** + * @var string + */ + public $server_backup; + + /** + * @var Price + */ + public $volume; + + /** + * @var array + */ + public $server_types; + + /** + * @var array + */ + public $load_balancer_types; /** - * Returns all pricing information. - * - * @see https://docs.hetzner.cloud/#pricing-get-all-prices - * * @param RequestOpts $requestOpts - * @return \stdClass|null + * @return Prices|null * * @throws \LKDev\HetznerCloud\APIException */ - public function all(?RequestOpts $requestOpts = null): ?\stdClass + public function all(?RequestOpts $requestOpts = null): ?self { if ($requestOpts == null) { $requestOpts = new RequestOpts(); } $response = $this->httpClient->get('pricing'.$requestOpts->buildQuery()); if (! HetznerAPIClient::hasError($response)) { - $this->prices = json_decode((string) $response->getBody())->pricing; + $payload = json_decode((string) $response->getBody())->pricing; - return $this->prices; + return $this->setAdditionalData($payload); } return null; } + + /** + * @param $input + * @return $this + */ + public function setAdditionalData($input) + { + $this->currency = $input->currency ?? null; + $this->vat_rate = $input->vat_rate ?? null; + $this->image = property_exists($input, 'image') ? Price::parse($input->image->price_per_gb_month) : null; + $this->floating_ip = property_exists($input, 'floating_ip') ? Price::parse($input->floating_ip->price_monthly) : null; + $this->traffic = property_exists($input, 'traffic') ? Price::parse($input->traffic->price_per_tb) : null; + $this->server_backup = property_exists($input, 'server_backup') ? ($input->server_backup->percentage ?? null) : null; + $this->volume = property_exists($input, 'volume') ? Price::parse($input->volume->price_per_gb_month) : null; + if (property_exists($input, 'server_types')) { + $this->server_types = array_map(function ($serverType) { + return ServerType::parse($serverType); + }, $input->server_types); + } + if (property_exists($input, 'load_balancer_types')) { + $this->load_balancer_types = array_map(function ($loadBalancerType) { + return LoadBalancerType::parse($loadBalancerType); + }, $input->load_balancer_types); + } + + return $this; + } + + /** + * @param $input + * @return array + */ + public static function parse($input) + { + if ($input == null) { + return []; + } + + return array_map(function ($price) { + return ServerTypePrice::parse($price); + }, $input); + } } diff --git a/src/Models/Prices/ServerTypePrice.php b/src/Models/Prices/ServerTypePrice.php new file mode 100644 index 00000000..48d60d1a --- /dev/null +++ b/src/Models/Prices/ServerTypePrice.php @@ -0,0 +1,48 @@ +location = $location; + $this->price_hourly = $priceHourly; + $this->price_monthly = $priceMonthly; + } + + /** + * @param $input + * @return self|null + */ + public static function parse($input): ?self + { + if ($input == null) { + return null; + } + + return new self($input->location ?? '', Price::parse($input->price_hourly ?? null), Price::parse($input->price_monthly ?? null)); + } +} diff --git a/src/Models/PrimaryIps/PrimaryIps.php b/src/Models/PrimaryIps/PrimaryIps.php index 76bdc1fe..c9b464be 100644 --- a/src/Models/PrimaryIps/PrimaryIps.php +++ b/src/Models/PrimaryIps/PrimaryIps.php @@ -159,9 +159,9 @@ public function create( */ public function setAdditionalData($input) { - $this->primary_ips = collect($input)->map(function ($primaryIp, $key) { + $this->primary_ips = array_map(function ($primaryIp) { return PrimaryIp::parse($primaryIp); - })->toArray(); + }, $input); return $this; } diff --git a/src/Models/SSHKeys/SSHKeys.php b/src/Models/SSHKeys/SSHKeys.php index 48ddae8c..a4828302 100644 --- a/src/Models/SSHKeys/SSHKeys.php +++ b/src/Models/SSHKeys/SSHKeys.php @@ -113,9 +113,9 @@ public function list(?RequestOpts $requestOpts = null): ?APIResponse */ public function setAdditionalData($input) { - $this->ssh_keys = collect($input)->map(function ($sshKey, $key) { + $this->ssh_keys = array_map(function ($sshKey) { return SSHKey::parse($sshKey); - })->toArray(); + }, $input); return $this; } diff --git a/src/Models/Servers/Server.php b/src/Models/Servers/Server.php index cf66e7ba..d790f104 100644 --- a/src/Models/Servers/Server.php +++ b/src/Models/Servers/Server.php @@ -17,6 +17,7 @@ use LKDev\HetznerCloud\Models\Datacenters\Datacenter; use LKDev\HetznerCloud\Models\Images\Image; use LKDev\HetznerCloud\Models\ISOs\ISO; +use LKDev\HetznerCloud\Models\Locations\Location; use LKDev\HetznerCloud\Models\Model; use LKDev\HetznerCloud\Models\Networks\Network; use LKDev\HetznerCloud\Models\Protection; @@ -79,9 +80,16 @@ class Server extends Model implements Resource /** * @var \LKDev\HetznerCloud\Models\Datacenters\Datacenter + * + * @deprecated Use $location instead */ public $datacenter; + /** + * @var \LKDev\HetznerCloud\Models\Locations\Location + */ + public $location; + /** * @var Image */ @@ -178,6 +186,16 @@ class Server extends Model implements Resource */ public $primaryDiskSize; + /** + * @var int|null + */ + public $placement_group; + + /** + * @var array + */ + public $load_balancers; + /** * @param int $serverId * @param GuzzleClient|null $httpClient @@ -194,34 +212,37 @@ public function __construct(int $serverId, ?GuzzleClient $httpClient = null) */ public function setAdditionalData($data) { - $this->name = $data->name; - $this->status = $data->status ?: null; - $this->public_net = $data->public_net ?: null; - $this->publicNet = $data->public_net ?: null; - $this->private_net = property_exists($data, 'private_net') ? $data->private_net : []; - $this->privateNet = property_exists($data, 'private_net') ? $data->private_net : []; - $this->server_type = $data->server_type ?: ServerType::parse($data->server_type); - $this->serverType = $data->server_type ?: ServerType::parse($data->server_type); - $this->datacenter = $data->datacenter ?: Datacenter::parse($data->datacenter); - $this->created = $data->created; - $this->image = $data->image ?: Image::parse($data->image); - $this->iso = $data->iso ?: ISO::parse($data->iso); + $this->name = $data->name ?? null; + $this->status = $data->status ?? null; + $this->public_net = $data->public_net ?? null; + $this->publicNet = $data->public_net ?? null; + $this->private_net = $data->private_net ?? []; + $this->privateNet = $data->private_net ?? []; + $this->server_type = property_exists($data, 'server_type') && $data->server_type ? ServerType::parse($data->server_type) : null; + $this->serverType = property_exists($data, 'server_type') && $data->server_type ? ServerType::parse($data->server_type) : null; + $this->datacenter = property_exists($data, 'datacenter') && $data->datacenter ? Datacenter::parse($data->datacenter) : null; + $this->location = property_exists($data, 'location') && $data->location ? Location::parse($data->location) : null; + $this->created = $data->created ?? null; + $this->image = property_exists($data, 'image') && $data->image ? Image::parse($data->image) : null; + $this->iso = property_exists($data, 'iso') && $data->iso ? ISO::parse($data->iso) : null; $this->rescue_enabled = $data->rescue_enabled ?? null; $this->rescueEnabled = $data->rescue_enabled ?? null; $this->locked = $data->locked ?? null; - $this->backup_window = $data->backup_window ?: null; - $this->backupWindow = $data->backup_window ?: null; - $this->outgoing_traffic = $data->outgoing_traffic ?: null; - $this->outgoingTraffic = $data->outgoing_traffic ?: null; - $this->ingoing_traffic = $data->ingoing_traffic ?: null; - $this->ingoingTraffic = $data->ingoing_traffic ?: null; - $this->included_traffic = $data->included_traffic ?: null; - $this->includedTraffic = $data->included_traffic ?: null; - $this->volumes = property_exists($data, 'volumes') ? $data->volumes : []; - $this->protection = $data->protection ?: Protection::parse($data->protection); - $this->labels = $data->labels; - $this->primary_disk_size = $data->primary_disk_size ?: null; - $this->primaryDiskSize = $data->primary_disk_size ?: null; + $this->backup_window = $data->backup_window ?? null; + $this->backupWindow = $data->backup_window ?? null; + $this->outgoing_traffic = $data->outgoing_traffic ?? null; + $this->outgoingTraffic = $data->outgoing_traffic ?? null; + $this->ingoing_traffic = $data->ingoing_traffic ?? null; + $this->ingoingTraffic = $data->ingoing_traffic ?? null; + $this->included_traffic = $data->included_traffic ?? null; + $this->includedTraffic = $data->included_traffic ?? null; + $this->volumes = $data->volumes ?? []; + $this->protection = property_exists($data, 'protection') && $data->protection ? Protection::parse($data->protection) : null; + $this->labels = property_exists($data, 'labels') && $data->labels ? get_object_vars($data->labels) : []; + $this->primary_disk_size = $data->primary_disk_size ?? null; + $this->primaryDiskSize = $data->primary_disk_size ?? null; + $this->placement_group = $data->placement_group ?? null; + $this->load_balancers = $data->load_balancers ?? []; return $this; } @@ -858,14 +879,15 @@ protected function replaceServerIdInUri(string $uri): string /** * @param $input + * @param GuzzleClient|null $httpClient * @return \LKDev\HetznerCloud\Models\Servers\Server|static |null */ - public static function parse($input) + public static function parse($input, ?GuzzleClient $httpClient = null) { if ($input == null) { return null; } - return (new self($input->id))->setAdditionalData($input); + return (new self($input->id, $httpClient))->setAdditionalData($input); } } diff --git a/src/Models/Servers/Servers.php b/src/Models/Servers/Servers.php index cbe81674..6334137b 100644 --- a/src/Models/Servers/Servers.php +++ b/src/Models/Servers/Servers.php @@ -144,7 +144,6 @@ public function deleteById(int $serverId): ?Action * @param string $name * @param \LKDev\HetznerCloud\Models\Servers\Types\ServerType $serverType * @param \LKDev\HetznerCloud\Models\Images\Image $image - * @param \LKDev\HetznerCloud\Models\Locations\Location $location * @param \LKDev\HetznerCloud\Models\Datacenters\Datacenter $datacenter * @param array $ssh_keys * @param bool $startAfterCreate @@ -155,9 +154,12 @@ public function deleteById(int $serverId): ?Action * @param array $labels * @param array $firewalls * @param array $public_net + * @param int|null $placement_group * @return APIResponse|null * * @throws \LKDev\HetznerCloud\APIException + * + * @deprecated Use createInLocation instead */ public function createInDatacenter( string $name, @@ -207,9 +209,9 @@ public function createInDatacenter( return APIResponse::create(array_merge([ 'action' => Action::parse($payload->action), 'server' => Server::parse($payload->server), - 'next_actions' => collect($payload->next_actions)->map(function ($action) { + 'next_actions' => array_map(function ($action) { return Action::parse($action); - })->toArray(), + }, $payload->next_actions), ], (property_exists($payload, 'root_password')) ? ['root_password' => $payload->root_password] : [] ), $response->getHeaders()); } @@ -284,9 +286,9 @@ public function createInLocation(string $name, return APIResponse::create(array_merge([ 'action' => Action::parse($payload->action), 'server' => Server::parse($payload->server), - 'next_actions' => collect($payload->next_actions)->map(function ($action) { + 'next_actions' => array_map(function ($action) { return Action::parse($action); - })->toArray(), + }, $payload->next_actions), ], (property_exists($payload, 'root_password')) ? ['root_password' => $payload->root_password] : [] ), $response->getHeaders()); } @@ -300,26 +302,21 @@ public function createInLocation(string $name, */ public function setAdditionalData($input) { - $this->servers = collect($input) - ->map(function ($server) { - if ($server != null) { - return Server::parse($server); - } - - return null; - }) - ->toArray(); + $this->servers = array_map(function ($server) { + return Server::parse($server, $this->httpClient); + }, array_filter($input)); return $this; } /** * @param $input + * @param \LKDev\HetznerCloud\Clients\GuzzleClient|null $httpClient * @return static */ - public static function parse($input) + public static function parse($input, ?\LKDev\HetznerCloud\Clients\GuzzleClient $httpClient = null) { - return (new self())->setAdditionalData($input); + return (new self($httpClient))->setAdditionalData($input); } /** diff --git a/src/Models/Servers/Types/ServerType.php b/src/Models/Servers/Types/ServerType.php index 17d5c6db..71851d6e 100644 --- a/src/Models/Servers/Types/ServerType.php +++ b/src/Models/Servers/Types/ServerType.php @@ -3,6 +3,8 @@ namespace LKDev\HetznerCloud\Models\Servers\Types; use LKDev\HetznerCloud\Models\Model; +use LKDev\HetznerCloud\Models\Prices\Prices; +use LKDev\HetznerCloud\Models\Prices\ServerTypePrice; class ServerType extends Model { @@ -41,6 +43,11 @@ class ServerType extends Model */ public $prices; + /** + * @var array + */ + public $price; + /** * @var string */ @@ -75,14 +82,15 @@ public function __construct(int $serverTypeId, string $name = '') public function setAdditionalData($input) { $this->name = $input->name; - $this->description = $input->description; - $this->cores = $input->cores; - $this->memory = $input->memory; - $this->disk = $input->disk; - $this->prices = $input->prices; - $this->storageType = $input->storage_type; - $this->cpuType = $input->cpu_type; - $this->architecture = $input->architecture; + $this->description = $input->description ?? null; + $this->cores = $input->cores ?? null; + $this->memory = $input->memory ?? null; + $this->disk = $input->disk ?? null; + $this->prices = Prices::parse($input->prices); + $this->price = property_exists($input, 'price') ? ServerTypePrice::parse($input->price) : null; + $this->storageType = $input->storage_type ?? null; + $this->cpuType = $input->cpu_type ?? null; + $this->architecture = property_exists($input, 'architecture') ? $input->architecture : null; return $this; } @@ -93,6 +101,10 @@ public function setAdditionalData($input) */ public static function parse($input) { + if ($input == null) { + return null; + } + return (new self($input->id))->setAdditionalData($input); } } diff --git a/src/Models/Servers/Types/ServerTypes.php b/src/Models/Servers/Types/ServerTypes.php index ac38ed03..a418eedb 100644 --- a/src/Models/Servers/Types/ServerTypes.php +++ b/src/Models/Servers/Types/ServerTypes.php @@ -102,9 +102,9 @@ public function getByName(string $name): ?ServerType */ public function setAdditionalData($input) { - $this->server_types = collect($input)->map(function ($serverType, $key) { + $this->server_types = array_map(function ($serverType) { return ServerType::parse($serverType); - })->toArray(); + }, $input); return $this; } diff --git a/src/Models/Volumes/Volume.php b/src/Models/Volumes/Volume.php index 2ba59fd2..e0d2e917 100644 --- a/src/Models/Volumes/Volume.php +++ b/src/Models/Volumes/Volume.php @@ -63,6 +63,11 @@ class Volume extends Model implements Resource */ public $linux_device; + /** + * @var string + */ + public $created; + /** * @param int $volumeId * @param GuzzleClient|null $httpClient @@ -85,9 +90,10 @@ public function setAdditionalData($data) $this->size = $data->size; $this->server = $data->server; - $this->location = Location::parse($data->location); - $this->protection = $data->protection ?: Protection::parse($data->protection); + $this->location = $data->location ? Location::parse($data->location) : null; + $this->protection = $data->protection ? Protection::parse($data->protection) : null; $this->labels = get_object_vars($data->labels); + $this->created = $data->created; return $this; } diff --git a/src/Models/Volumes/Volumes.php b/src/Models/Volumes/Volumes.php index 4b8fcf95..7d3c8688 100644 --- a/src/Models/Volumes/Volumes.php +++ b/src/Models/Volumes/Volumes.php @@ -135,12 +135,12 @@ public function create(string $name, int $size, ?Server $server = null, ?Locatio 'size' => $size, 'automount' => $automount, ]; - if ($location == null && $server != null) { + if ($server != null) { $parameters['server'] = $server->id; - } elseif ($location != null && $server == null) { - $parameters['location'] = $location->name ?: $location->id; + } elseif ($location != null) { + $parameters['location'] = $location->id; } else { - throw new \InvalidArgumentException('Please specify only a server or a location'); + throw new \InvalidArgumentException('Please specify either a server or a location'); } if ($format != null) { $parameters['format'] = $format; @@ -157,9 +157,9 @@ public function create(string $name, int $size, ?Server $server = null, ?Locatio return APIResponse::create([ 'action' => Action::parse($data->action), 'volume' => Volume::parse($data->volume), - 'next_actions' => collect($data->next_actions)->map(function ($action) { + 'next_actions' => array_map(function ($action) { return Action::parse($action); - })->toArray(), + }, $data->next_actions), ], $response->getHeaders()); } @@ -172,13 +172,13 @@ public function create(string $name, int $size, ?Server $server = null, ?Locatio */ public function setAdditionalData($input) { - $this->volumes = collect($input)->map(function ($volume, $key) { + $this->volumes = array_map(function ($volume) { if ($volume != null) { return Volume::parse($volume); } return null; - })->toArray(); + }, $input); return $this; } diff --git a/src/Models/Zones/Zones.php b/src/Models/Zones/Zones.php index eb224d31..abdd14f4 100644 --- a/src/Models/Zones/Zones.php +++ b/src/Models/Zones/Zones.php @@ -188,15 +188,13 @@ public function deleteById(int $zoneId): ?Action */ public function setAdditionalData($input) { - $this->zones = collect($input) - ->map(function ($zone) { - if ($zone != null) { - return Zone::parse($zone); - } - - return null; - }) - ->toArray(); + $this->zones = array_map(function ($zone) { + if ($zone != null) { + return Zone::parse($zone); + } + + return null; + }, $input); return $this; } diff --git a/src/RequestOpts.php b/src/RequestOpts.php index c5c494f9..c2465f45 100644 --- a/src/RequestOpts.php +++ b/src/RequestOpts.php @@ -51,10 +51,9 @@ public function __construct(?int $perPage = null, ?int $page = null, ?string $la */ public function buildQuery() { - $values = collect(get_object_vars($this)) - ->filter(function ($var) { - return $var != null; - })->toArray(); + $values = array_filter(get_object_vars($this), function ($var) { + return $var != null; + }); return count($values) == 0 ? '' : ('?'.http_build_query($values)); } diff --git a/src/Traits/GetFunctionTrait.php b/src/Traits/GetFunctionTrait.php index 4869ba02..ab743d16 100644 --- a/src/Traits/GetFunctionTrait.php +++ b/src/Traits/GetFunctionTrait.php @@ -9,13 +9,11 @@ trait GetFunctionTrait { public function get($nameOrId) { - try { + if (is_numeric($nameOrId)) { return $this->getById((int) $nameOrId); - } catch (\Exception $e) { - unset($e); - - return $this->getByName($nameOrId); } + + return $this->getByName($nameOrId); } protected function _all(RequestOpts $requestOpts) diff --git a/tests/Unit/Models/Certificates/CertificateTest.php b/tests/Unit/Models/Certificates/CertificateTest.php new file mode 100644 index 00000000..336bcdc0 --- /dev/null +++ b/tests/Unit/Models/Certificates/CertificateTest.php @@ -0,0 +1,56 @@ +certificate = Certificate::parse($tmp->certificate); + } + + public function testUpdate() + { + $this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/certificate.json'))); + $updated = $this->certificate->update(['name' => 'New Name']); + $this->assertInstanceOf(Certificate::class, $updated); + $this->assertLastRequestEquals('PUT', '/certificates/897'); + $this->assertLastRequestBodyParametersEqual(['name' => 'New Name']); + } + + public function testDelete() + { + $this->mockHandler->append(new Response(204, [])); + $this->assertTrue($this->certificate->delete()); + $this->assertLastRequestEquals('DELETE', '/certificates/897'); + } + + public function testReload() + { + $this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/certificate.json'))); + $reloaded = $this->certificate->reload(); + $this->assertInstanceOf(Certificate::class, $reloaded); + $this->assertLastRequestEquals('GET', '/certificates/897'); + } + + public function testParse() + { + $tmp = json_decode(file_get_contents(__DIR__.'/fixtures/certificate.json')); + $parsed = Certificate::parse($tmp->certificate); + $this->assertEquals($this->certificate->id, $parsed->id); + $this->assertEquals($this->certificate->name, $parsed->name); + $this->assertEquals($this->certificate->certificate, $parsed->certificate); + $this->assertEquals($this->certificate->labels, $parsed->labels); + } +} diff --git a/tests/Unit/Models/Datacenters/DatacenterTest.php b/tests/Unit/Models/Datacenters/DatacenterTest.php new file mode 100644 index 00000000..a5046d3a --- /dev/null +++ b/tests/Unit/Models/Datacenters/DatacenterTest.php @@ -0,0 +1,51 @@ +datacenter = Datacenter::parse($tmp->datacenter); + } + + public function testReload() + { + $this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/datacenter.json'))); + $reloaded = $this->datacenter->reload(); + $this->assertInstanceOf(Datacenter::class, $reloaded); + $this->assertLastRequestEquals('GET', '/datacenters/1'); + } + + public function testParse() + { + $tmp = json_decode(file_get_contents(__DIR__.'/fixtures/datacenter.json')); + $parsed = Datacenter::parse($tmp->datacenter); + $this->assertEquals($this->datacenter->id, $parsed->id); + $this->assertEquals($this->datacenter->name, $parsed->name); + $this->assertEquals($this->datacenter->description, $parsed->description); + } + + public function testDelete() + { + $this->expectException(\BadMethodCallException::class); + $this->datacenter->delete(); + } + + public function testUpdate() + { + $this->expectException(\BadMethodCallException::class); + $this->datacenter->update([]); + } +} diff --git a/tests/Unit/Models/Firewalls/FirewallTest.php b/tests/Unit/Models/Firewalls/FirewallTest.php index 75874ffc..26cedf1c 100644 --- a/tests/Unit/Models/Firewalls/FirewallTest.php +++ b/tests/Unit/Models/Firewalls/FirewallTest.php @@ -41,7 +41,7 @@ public function setUp(): void public function testDelete() { $this->mockHandler->append(new Response(204, [])); - $this->assertTrue($this->firewall->delete()); + $this->assertInstanceOf(\LKDev\HetznerCloud\APIResponse::class, $this->firewall->delete()); } /** diff --git a/tests/Unit/Models/Firewalls/FirewallsTest.php b/tests/Unit/Models/Firewalls/FirewallsTest.php index 6b527e9a..f41393ef 100644 --- a/tests/Unit/Models/Firewalls/FirewallsTest.php +++ b/tests/Unit/Models/Firewalls/FirewallsTest.php @@ -68,8 +68,9 @@ public function testGetByName() $this->assertCount(3, $firewall->rules[0]->sourceIPs); $this->assertCount(3, $firewall->rules[0]->destinationIPs); - $this->assertCount(1, $firewall->appliedTo); + $this->assertCount(2, $firewall->appliedTo); $this->assertInstanceOf(FirewallResource::class, $firewall->appliedTo[0]); + $this->assertInstanceOf(FirewallResource::class, $firewall->appliedTo[1]); $this->assertEmpty($firewall->labels); @@ -88,8 +89,9 @@ public function testGet() $this->assertCount(1, $firewall->rules); $this->assertInstanceOf(FirewallRule::class, $firewall->rules[0]); - $this->assertCount(1, $firewall->appliedTo); + $this->assertCount(2, $firewall->appliedTo); $this->assertInstanceOf(FirewallResource::class, $firewall->appliedTo[0]); + $this->assertInstanceOf(FirewallResource::class, $firewall->appliedTo[1]); $this->assertEmpty($firewall->labels); diff --git a/tests/Unit/Models/FloatingIPs/FloatingIPsTest.php b/tests/Unit/Models/FloatingIPs/FloatingIPsTest.php index a1b2ca32..d5efcbba 100644 --- a/tests/Unit/Models/FloatingIPs/FloatingIPsTest.php +++ b/tests/Unit/Models/FloatingIPs/FloatingIPsTest.php @@ -76,7 +76,8 @@ public function testList() public function testCreateWithLocation() { $this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/floatingIP.json'))); - $floatingIp = $this->floatingIps->create('ipv4', 'Web Frontend', new Location(123, 'nbg1'), null, 'my-fip', ['key' => 'value']); + $apiResponse = $this->floatingIps->create('ipv4', 'Web Frontend', new Location(123, 'nbg1'), null, 'my-fip', ['key' => 'value']); + $floatingIp = $apiResponse->floating_ip; $this->assertEquals($floatingIp->id, 4711); $this->assertEquals($floatingIp->description, 'Web Frontend'); @@ -90,7 +91,8 @@ public function testCreateWithLocation() public function testCreateWithServer() { $this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/floatingIP.json'))); - $floatingIp = $this->floatingIps->create('ipv4', 'Web Frontend', null, new Server(23)); + $apiResponse = $this->floatingIps->create('ipv4', 'Web Frontend', null, new Server(23)); + $floatingIp = $apiResponse->floating_ip; $this->assertEquals($floatingIp->id, 4711); $this->assertEquals($floatingIp->description, 'Web Frontend'); @@ -104,7 +106,8 @@ public function testCreateWithServer() public function testCreateWithName() { $this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/floatingIP.json'))); - $floatingIp = $this->floatingIps->create('ipv4', 'Web Frontend', new Location(123, 'nbg1'), null, 'WebServer'); + $apiResponse = $this->floatingIps->create('ipv4', 'Web Frontend', new Location(123, 'nbg1'), null, 'WebServer'); + $floatingIp = $apiResponse->floating_ip; $this->assertEquals($floatingIp->id, 4711); $this->assertEquals($floatingIp->description, 'Web Frontend'); @@ -122,7 +125,7 @@ public function testDelete() $this->assertLastRequestEquals('GET', '/floating_ips/4711'); $this->mockHandler->append(new Response(204, [])); - $this->assertTrue($floatingIp->delete()); + $this->assertInstanceOf(\LKDev\HetznerCloud\APIResponse::class, $floatingIp->delete()); $this->assertLastRequestEquals('DELETE', '/floating_ips/4711'); } } diff --git a/tests/Unit/Models/FloatingIPs/FloatingIpTest.php b/tests/Unit/Models/FloatingIPs/FloatingIpTest.php index aa1557f9..6e633cff 100644 --- a/tests/Unit/Models/FloatingIPs/FloatingIpTest.php +++ b/tests/Unit/Models/FloatingIPs/FloatingIpTest.php @@ -53,7 +53,7 @@ public function testChangeProtection() public function testDelete() { $this->mockHandler->append(new Response(204, [])); - $this->assertTrue($this->floatingIp->delete()); + $this->assertInstanceOf(\LKDev\HetznerCloud\APIResponse::class, $this->floatingIp->delete()); } /** diff --git a/tests/Unit/Models/ISO/ISOTest.php b/tests/Unit/Models/ISO/ISOTest.php new file mode 100644 index 00000000..c847e3b0 --- /dev/null +++ b/tests/Unit/Models/ISO/ISOTest.php @@ -0,0 +1,52 @@ +iso = ISO::parse($tmp->iso); + } + + public function testReload() + { + $this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/iso.json'))); + $reloaded = $this->iso->reload(); + $this->assertInstanceOf(ISO::class, $reloaded); + $this->assertLastRequestEquals('GET', '/isos/4711'); + } + + public function testParse() + { + $tmp = json_decode(file_get_contents(__DIR__.'/fixtures/iso.json')); + $parsed = ISO::parse($tmp->iso); + $this->assertEquals($this->iso->id, $parsed->id); + $this->assertEquals($this->iso->name, $parsed->name); + $this->assertEquals($this->iso->description, $parsed->description); + $this->assertEquals($this->iso->type, $parsed->type); + } + + public function testDelete() + { + $this->expectException(\BadMethodCallException::class); + $this->iso->delete(); + } + + public function testUpdate() + { + $this->expectException(\BadMethodCallException::class); + $this->iso->update([]); + } +} diff --git a/tests/Unit/Models/LoadBalancerTypes/LoadBalancerTypesTest.php b/tests/Unit/Models/LoadBalancerTypes/LoadBalancerTypesTest.php index d8ea7783..0f5901f8 100644 --- a/tests/Unit/Models/LoadBalancerTypes/LoadBalancerTypesTest.php +++ b/tests/Unit/Models/LoadBalancerTypes/LoadBalancerTypesTest.php @@ -3,7 +3,9 @@ namespace LKDev\Tests\Unit\Models\LoadBalancerTypes; use GuzzleHttp\Psr7\Response; +use LKDev\HetznerCloud\Models\LoadBalancerTypes\LoadBalancerType; use LKDev\HetznerCloud\Models\LoadBalancerTypes\LoadBalancerTypes; +use LKDev\HetznerCloud\Models\Prices\ServerTypePrice; use LKDev\Tests\TestCase; class LoadBalancerTypesTest extends TestCase @@ -63,4 +65,13 @@ public function testList() $this->assertEquals($loadBalancerTypes[0]->name, 'lb11'); $this->assertLastRequestEquals('GET', '/load_balancer_types'); } + + public function testParseWithPrice() + { + $input = json_decode(file_get_contents(__DIR__.'/fixtures/loadBalancerType.json')); + $input->load_balancer_type->price = $input->load_balancer_type->prices[0]; + $loadBalancerType = LoadBalancerType::parse($input->load_balancer_type); + $this->assertInstanceOf(ServerTypePrice::class, $loadBalancerType->price); + $this->assertEquals('fsn1', $loadBalancerType->price->location); + } } diff --git a/tests/Unit/Models/Locations/LocationTest.php b/tests/Unit/Models/Locations/LocationTest.php new file mode 100644 index 00000000..e2e4187c --- /dev/null +++ b/tests/Unit/Models/Locations/LocationTest.php @@ -0,0 +1,51 @@ +location = Location::parse($tmp->location); + } + + public function testReload() + { + $this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/location.json'))); + $reloaded = $this->location->reload(); + $this->assertInstanceOf(Location::class, $reloaded); + $this->assertLastRequestEquals('GET', '/locations/1'); + } + + public function testParse() + { + $tmp = json_decode(file_get_contents(__DIR__.'/fixtures/location.json')); + $parsed = Location::parse($tmp->location); + $this->assertEquals($this->location->id, $parsed->id); + $this->assertEquals($this->location->name, $parsed->name); + $this->assertEquals($this->location->description, $parsed->description); + } + + public function testDelete() + { + $this->expectException(\BadMethodCallException::class); + $this->location->delete(); + } + + public function testUpdate() + { + $this->expectException(\BadMethodCallException::class); + $this->location->update([]); + } +} diff --git a/tests/Unit/Models/Networks/NetworksTest.php b/tests/Unit/Models/Networks/NetworksTest.php index b74df239..eb6b0548 100644 --- a/tests/Unit/Models/Networks/NetworksTest.php +++ b/tests/Unit/Models/Networks/NetworksTest.php @@ -106,12 +106,12 @@ public function testGet() public function testBasicCreate() { $this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/network.json'))); - $resp = $this->networks->create('mynet', '10.0.0.0/16'); + $resp = $this->networks->create('mynet', '10.0.0.0/16', [], [], [], true); $this->assertInstanceOf(APIResponse::class, $resp); $this->assertInstanceOf(Network::class, $resp->getResponsePart('network')); $this->assertLastRequestEquals('POST', '/networks'); - $this->assertLastRequestBodyParametersEqual(['name' => 'mynet', 'ip_range' => '10.0.0.0/16']); + $this->assertLastRequestBodyParametersEqual(['name' => 'mynet', 'ip_range' => '10.0.0.0/16', 'expose_routes_to_vswitch' => true]); } public function testAdvancedCreate() diff --git a/tests/Unit/Models/Networks/SubnetAndRouteTest.php b/tests/Unit/Models/Networks/SubnetAndRouteTest.php new file mode 100644 index 00000000..8dc9fcf9 --- /dev/null +++ b/tests/Unit/Models/Networks/SubnetAndRouteTest.php @@ -0,0 +1,57 @@ +network->subnets); + $this->assertIsArray($subnets); + $this->assertCount(1, $subnets); + $subnet = $subnets[0]; + $this->assertInstanceOf(Subnet::class, $subnet); + $this->assertEquals('cloud', $subnet->type); + $this->assertEquals('10.0.1.0/24', $subnet->ipRange); + $this->assertEquals('eu-central', $subnet->networkZone); + $this->assertEquals('10.0.0.1', $subnet->gateway); + } + + public function testSubnetToRequestPayload() + { + $subnet = new Subnet('cloud', '10.0.1.0/24', 'eu-central'); + $payload = $subnet->__toRequestPayload(); + $this->assertEquals([ + 'type' => 'cloud', + 'ip_range' => '10.0.1.0/24', + 'network_zone' => 'eu-central', + ], $payload); + } + + public function testRouteParse() + { + $tmp = json_decode(file_get_contents(__DIR__.'/fixtures/network.json')); + $routes = Route::parse($tmp->network->routes); + $this->assertIsArray($routes); + $this->assertCount(1, $routes); + $route = $routes[0]; + $this->assertInstanceOf(Route::class, $route); + $this->assertEquals('10.100.1.0/24', $route->destination); + $this->assertEquals('10.0.1.1', $route->gateway); + } + + public function testRouteToRequestPayload() + { + $route = new Route('10.100.1.0/24', '10.0.1.1'); + $payload = $route->__toRequestPayload(); + $this->assertEquals([ + 'destination' => '10.100.1.0/24', + 'gateway' => '10.0.1.1', + ], $payload); + } +} diff --git a/tests/Unit/Models/PlacementGroups/PlacementGroupTest.php b/tests/Unit/Models/PlacementGroups/PlacementGroupTest.php deleted file mode 100644 index b835e7a4..00000000 --- a/tests/Unit/Models/PlacementGroups/PlacementGroupTest.php +++ /dev/null @@ -1,24 +0,0 @@ -hetznerApi->getHttpClient()); - $this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/placement_group.json'))); - $this->placement_group = $tmp->get(4862); - } -} diff --git a/tests/Unit/Models/Pricing/PricingTest.php b/tests/Unit/Models/Pricing/PricingTest.php index 07b41fdc..0e9d19b8 100644 --- a/tests/Unit/Models/Pricing/PricingTest.php +++ b/tests/Unit/Models/Pricing/PricingTest.php @@ -10,7 +10,10 @@ namespace LKDev\Tests\Unit\Pricing; use GuzzleHttp\Psr7\Response; +use LKDev\HetznerCloud\Models\Prices\Price; use LKDev\HetznerCloud\Models\Prices\Prices; +use LKDev\HetznerCloud\Models\Prices\ServerTypePrice; +use LKDev\HetznerCloud\Models\Servers\Types\ServerType; use LKDev\Tests\TestCase; class PricingTest extends TestCase @@ -32,8 +35,21 @@ public function testAll() $prices = $this->prices->all(); $this->assertEquals('EUR', $prices->currency); $this->assertEquals('19.000000', $prices->vat_rate); - $this->assertEquals('1.0000000000', $prices->image->price_per_gb_month->net); + $this->assertInstanceOf(Price::class, $prices->image); + $this->assertEquals('1.0000000000', $prices->image->net); + $this->assertEquals('1.1900000000000000', $prices->image->gross); + + $this->assertInstanceOf(Price::class, $prices->floating_ip); + $this->assertInstanceOf(Price::class, $prices->traffic); + $this->assertEquals('20.0000000000', $prices->server_backup); + $this->assertInstanceOf(Price::class, $prices->volume); + $this->assertIsArray($prices->server_types); + $this->assertInstanceOf(ServerType::class, $prices->server_types[0]); + $this->assertIsArray($prices->server_types[0]->prices); + $this->assertInstanceOf(ServerTypePrice::class, $prices->server_types[0]->prices[0]); + + $this->assertIsArray($prices->load_balancer_types); $this->assertLastRequestEquals('GET', '/pricing'); } } diff --git a/tests/Unit/Models/SSHKeys/SSHKeyTest.php b/tests/Unit/Models/SSHKeys/SSHKeyTest.php new file mode 100644 index 00000000..f78c761e --- /dev/null +++ b/tests/Unit/Models/SSHKeys/SSHKeyTest.php @@ -0,0 +1,66 @@ +sshKey = SSHKey::parse($tmp->ssh_key); + } + + public function testUpdate() + { + $this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/ssh_key.json'))); + $updated = $this->sshKey->update(['name' => 'New Name']); + $this->assertInstanceOf(SSHKey::class, $updated); + $this->assertLastRequestEquals('PUT', '/ssh_keys/2323'); + $this->assertLastRequestBodyParametersEqual(['name' => 'New Name']); + } + + public function testChangeName() + { + $this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/ssh_key.json'))); + $updated = $this->sshKey->changeName('New Name'); + $this->assertInstanceOf(SSHKey::class, $updated); + $this->assertLastRequestEquals('PUT', '/ssh_keys/2323'); + $this->assertLastRequestBodyParametersEqual(['name' => 'New Name']); + } + + public function testDelete() + { + $this->mockHandler->append(new Response(204, [])); + $this->assertTrue($this->sshKey->delete()); + $this->assertLastRequestEquals('DELETE', '/ssh_keys/2323'); + } + + public function testReload() + { + $this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/ssh_key.json'))); + $reloaded = $this->sshKey->reload(); + $this->assertInstanceOf(SSHKey::class, $reloaded); + $this->assertLastRequestEquals('GET', '/ssh_keys/2323'); + } + + public function testParse() + { + $tmp = json_decode(file_get_contents(__DIR__.'/fixtures/ssh_key.json')); + $parsed = SSHKey::parse($tmp->ssh_key); + $this->assertEquals($this->sshKey->id, $parsed->id); + $this->assertEquals($this->sshKey->name, $parsed->name); + $this->assertEquals($this->sshKey->fingerprint, $parsed->fingerprint); + $this->assertEquals($this->sshKey->public_key, $parsed->public_key); + $this->assertEquals($this->sshKey->labels, $parsed->labels); + } +} diff --git a/tests/Unit/Models/Servers/ServerTest.php b/tests/Unit/Models/Servers/ServerTest.php index 9d723219..31b1353e 100644 --- a/tests/Unit/Models/Servers/ServerTest.php +++ b/tests/Unit/Models/Servers/ServerTest.php @@ -255,6 +255,9 @@ public function testReload() $this->assertEquals($server->id, 42); $this->assertEquals($server->name, 'my-server'); $this->assertEquals($server->status, 'running'); + $this->assertInstanceOf(\LKDev\HetznerCloud\Models\Locations\Location::class, $server->location); + $this->assertEquals($server->location->id, 1); + $this->assertEquals($server->location->name, 'fsn1'); } public function testMetrics() diff --git a/tests/Unit/Models/Servers/Types/ServerTypeTest.php b/tests/Unit/Models/Servers/Types/ServerTypeTest.php new file mode 100644 index 00000000..8614f7b6 --- /dev/null +++ b/tests/Unit/Models/Servers/Types/ServerTypeTest.php @@ -0,0 +1,32 @@ +server->server_type); + $this->assertInstanceOf(ServerType::class, $serverType); + $this->assertEquals(1, $serverType->id); + $this->assertEquals('cx11', $serverType->name); + $this->assertEquals('CX11', $serverType->description); + $this->assertEquals(1, $serverType->cores); + $this->assertEquals(1, $serverType->memory); + $this->assertEquals(25, $serverType->disk); + $this->assertEquals('local', $serverType->storageType); + $this->assertEquals('shared', $serverType->cpuType); + $this->assertNull($serverType->architecture); + + $inputWithPrice = $tmp->server->server_type; + $inputWithPrice->price = $inputWithPrice->prices[0]; + $serverTypeWithPrice = ServerType::parse($inputWithPrice); + $this->assertInstanceOf(ServerTypePrice::class, $serverTypeWithPrice->price); + $this->assertEquals('fsn1', $serverTypeWithPrice->price->location); + } +} diff --git a/tests/Unit/Models/Servers/fixtures/server.json b/tests/Unit/Models/Servers/fixtures/server.json index 3b404978..fe5fe531 100644 --- a/tests/Unit/Models/Servers/fixtures/server.json +++ b/tests/Unit/Models/Servers/fixtures/server.json @@ -88,6 +88,16 @@ ] } }, + "location": { + "id": 1, + "name": "fsn1", + "description": "Falkenstein DC Park 1", + "country": "DE", + "city": "Falkenstein", + "latitude": 50.47612, + "longitude": 12.370071, + "network_zone": "eu-central" + }, "image": { "id": 4711, "type": "snapshot", diff --git a/tests/Unit/Models/Volumes/VolumesTest.php b/tests/Unit/Models/Volumes/VolumesTest.php index 9b109d8a..0b2a07ec 100644 --- a/tests/Unit/Models/Volumes/VolumesTest.php +++ b/tests/Unit/Models/Volumes/VolumesTest.php @@ -35,7 +35,7 @@ public function testCreate() $this->assertIsArray($resp->next_actions); $this->assertLastRequestEquals('POST', '/volumes'); - $this->assertLastRequestBodyParametersEqual(['name' => 'database-storage', 'size' => 42, 'location' => 'nbg1']); + $this->assertLastRequestBodyParametersEqual(['name' => 'database-storage', 'size' => 42, 'location' => 1, 'automount' => false]); } public function testGetByName()