Skip to content

Commit e4f4ea8

Browse files
committed
Add tests and implementations for certificate, server, load balancer, and zone actions
- Add `retry` method to the Certificate model with corresponding tests for retrying issuance or renewal. - Implement `addToPlacementGroup` and `removeFromPlacementGroup` methods in the Server model, including unit tests. - Add `create` method to the LoadBalancer model for creating new load balancers with related tests. - Introduce `metrics` method in the LoadBalancer model for retrieving metrics, along with tests. - Add `updateRecords` method to the Zone RRSet model to handle record updates and include tests.
1 parent 003f895 commit e4f4ea8

16 files changed

Lines changed: 442 additions & 0 deletions

src/Models/Certificates/Certificate.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99

1010
namespace LKDev\HetznerCloud\Models\Certificates;
1111

12+
use LKDev\HetznerCloud\APIResponse;
1213
use LKDev\HetznerCloud\HetznerAPIClient;
14+
use LKDev\HetznerCloud\Models\Actions\Action;
1315
use LKDev\HetznerCloud\Models\Contracts\Resource;
1416
use LKDev\HetznerCloud\Models\Model;
1517

@@ -145,6 +147,27 @@ public static function parse($input)
145147
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);
146148
}
147149

150+
/**
151+
* Retry a failed Certificate issuance or renewal (only for managed certificates).
152+
*
153+
* @see https://docs.hetzner.cloud/#certificate-actions-retry-issuance-or-renewal
154+
*
155+
* @return APIResponse|null
156+
*
157+
* @throws \LKDev\HetznerCloud\APIException
158+
*/
159+
public function retry(): ?APIResponse
160+
{
161+
$response = $this->httpClient->post('certificates/'.$this->id.'/actions/retry', []);
162+
if (! HetznerAPIClient::hasError($response)) {
163+
return APIResponse::create([
164+
'action' => Action::parse(json_decode((string) $response->getBody())->action),
165+
], $response->getHeaders());
166+
}
167+
168+
return null;
169+
}
170+
148171
/**
149172
* Reload the data of the SSH Key.
150173
*

src/Models/LoadBalancers/LoadBalancer.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,36 @@ public function removeTarget(string $type, ?LoadBalancerTargetIp $ip = null, ?ar
553553
return null;
554554
}
555555

556+
/**
557+
* Get Metrics for a Load Balancer.
558+
*
559+
* @see https://docs.hetzner.cloud/#load-balancers-get-metrics-for-a-load-balancer
560+
*
561+
* @param string $type Comma-separated list of metric types (open_connections, connections_per_second, requests_per_second, bandwidth)
562+
* @param string $start Start of period (ISO 8601 date-time)
563+
* @param string $end End of period (ISO 8601 date-time)
564+
* @param int|null $step Resolution of results in seconds
565+
* @return APIResponse|null
566+
*
567+
* @throws APIException
568+
* @throws GuzzleException
569+
*/
570+
public function metrics(string $type, string $start, string $end, ?int $step = null): ?APIResponse
571+
{
572+
$params = compact('type', 'start', 'end');
573+
if ($step !== null) {
574+
$params['step'] = $step;
575+
}
576+
$response = $this->httpClient->get($this->replaceServerIdInUri('load_balancers/{id}/metrics?').http_build_query($params));
577+
if (! HetznerAPIClient::hasError($response)) {
578+
return APIResponse::create([
579+
'metrics' => json_decode((string) $response->getBody())->metrics,
580+
], $response->getHeaders());
581+
}
582+
583+
return null;
584+
}
585+
556586
/**
557587
* Updates a Load Balancer Service.
558588
*

src/Models/LoadBalancers/LoadBalancers.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use LKDev\HetznerCloud\APIResponse;
66
use LKDev\HetznerCloud\HetznerAPIClient;
7+
use LKDev\HetznerCloud\Models\Actions\Action;
78
use LKDev\HetznerCloud\Models\Contracts\Resources;
89
use LKDev\HetznerCloud\Models\Meta;
910
use LKDev\HetznerCloud\Models\Model;
@@ -66,6 +67,69 @@ public function list(?RequestOpts $requestOpts = null): ?APIResponse
6667
return null;
6768
}
6869

70+
/**
71+
* Creates a Load Balancer.
72+
*
73+
* @see https://docs.hetzner.cloud/#load-balancers-create-a-load-balancer
74+
*
75+
* @param string $name
76+
* @param string $loadBalancerType ID or name of the Load Balancer type
77+
* @param string|null $location ID or name of location (mutually exclusive with $networkZone)
78+
* @param string|null $networkZone Name of network zone (mutually exclusive with $location)
79+
* @param array|null $algorithm
80+
* @param array $labels
81+
* @param int|null $network
82+
* @param bool $publicInterface
83+
* @param array $services
84+
* @param array $targets
85+
* @return APIResponse|null
86+
*
87+
* @throws \LKDev\HetznerCloud\APIException
88+
*/
89+
public function create(string $name, string $loadBalancerType, ?string $location = null, ?string $networkZone = null, ?array $algorithm = null, array $labels = [], ?int $network = null, bool $publicInterface = true, array $services = [], array $targets = []): ?APIResponse
90+
{
91+
$payload = [
92+
'name' => $name,
93+
'load_balancer_type' => $loadBalancerType,
94+
];
95+
if ($location !== null) {
96+
$payload['location'] = $location;
97+
}
98+
if ($networkZone !== null) {
99+
$payload['network_zone'] = $networkZone;
100+
}
101+
if ($algorithm !== null) {
102+
$payload['algorithm'] = $algorithm;
103+
}
104+
if (! empty($labels)) {
105+
$payload['labels'] = $labels;
106+
}
107+
if ($network !== null) {
108+
$payload['network'] = $network;
109+
}
110+
if (! $publicInterface) {
111+
$payload['public_interface'] = false;
112+
}
113+
if (! empty($services)) {
114+
$payload['services'] = $services;
115+
}
116+
if (! empty($targets)) {
117+
$payload['targets'] = $targets;
118+
}
119+
120+
$response = $this->httpClient->post('load_balancers', ['json' => $payload]);
121+
if (! HetznerAPIClient::hasError($response)) {
122+
$body = json_decode((string) $response->getBody());
123+
124+
return APIResponse::create([
125+
'load_balancer' => LoadBalancer::parse($body->load_balancer),
126+
'action' => Action::parse($body->action),
127+
], $response->getHeaders());
128+
}
129+
130+
return null;
131+
}
132+
69133
/**
70134
* Gets a specific Load Balancer object.
71135
*

src/Models/Servers/Server.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,51 @@ public function changeAliasIPs(Network $network, array $aliasIps)
868868
return null;
869869
}
870870

871+
/**
872+
* Adds a Server to a Placement Group.
873+
*
874+
* @see https://docs.hetzner.cloud/#server-actions-add-a-server-to-a-placement-group
875+
*
876+
* @param int $placementGroupId
877+
* @return APIResponse|null
878+
*
879+
* @throws \LKDev\HetznerCloud\APIException
880+
*/
881+
public function addToPlacementGroup(int $placementGroupId): ?APIResponse
882+
{
883+
$response = $this->httpClient->post($this->replaceServerIdInUri('servers/{id}/actions/add_to_placement_group'), [
884+
'json' => ['placement_group' => $placementGroupId],
885+
]);
886+
if (! HetznerAPIClient::hasError($response)) {
887+
return APIResponse::create([
888+
'action' => Action::parse(json_decode((string) $response->getBody())->action),
889+
], $response->getHeaders());
890+
}
891+
892+
return null;
893+
}
894+
895+
/**
896+
* Removes a Server from a Placement Group.
897+
*
898+
* @see https://docs.hetzner.cloud/#server-actions-remove-a-server-from-a-placement-group
899+
*
900+
* @return APIResponse|null
901+
*
902+
* @throws \LKDev\HetznerCloud\APIException
903+
*/
904+
public function removeFromPlacementGroup(): ?APIResponse
905+
{
906+
$response = $this->httpClient->post($this->replaceServerIdInUri('servers/{id}/actions/remove_from_placement_group'), []);
907+
if (! HetznerAPIClient::hasError($response)) {
908+
return APIResponse::create([
909+
'action' => Action::parse(json_decode((string) $response->getBody())->action),
910+
], $response->getHeaders());
911+
}
912+
913+
return null;
914+
}
915+
871916
/**
872917
* @param string $uri
873918
* @return string

src/Models/Zones/RRSet.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,4 +255,30 @@ public function removeRecords(array $records)
255255

256256
return null;
257257
}
258+
259+
/**
260+
* Update specific records in this RRSet.
261+
*
262+
* @see https://docs.hetzner.cloud/#zone-rrset-actions-update-records-in-a-rrset
263+
*
264+
* @param array<array{value: string, comment: string}> $records
265+
* @return APIResponse|null
266+
*
267+
* @throws \LKDev\HetznerCloud\APIException
268+
*/
269+
public function updateRecords(array $records): ?APIResponse
270+
{
271+
$response = $this->httpClient->post('zones/'.$this->zone.'/rrsets/'.$this->id.'/actions/update_records', [
272+
'json' => [
273+
'records' => $records,
274+
],
275+
]);
276+
if (! HetznerAPIClient::hasError($response)) {
277+
return APIResponse::create([
278+
'action' => Action::parse(json_decode((string) $response->getBody())->action),
279+
], $response->getHeaders());
280+
}
281+
282+
return null;
283+
}
258284
}

tests/Unit/Models/Certificates/CertificatesTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,19 @@ public function testDelete()
9494
$this->assertTrue($certificate->delete());
9595
$this->assertLastRequestEquals('DELETE', '/certificates/897');
9696
}
97+
98+
public function testRetry()
99+
{
100+
$this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/certificate.json')));
101+
$certificate = $this->certificates->get(897);
102+
103+
$this->mockHandler->append(new Response(201, [], file_get_contents(__DIR__.'/fixtures/certificate_action_retry.json')));
104+
$apiResponse = $certificate->retry();
105+
106+
$this->assertEquals('retry_issuance_or_renewal', $apiResponse->action->command);
107+
$this->assertEquals(897, $apiResponse->action->resources[0]->id);
108+
$this->assertEquals('certificate', $apiResponse->action->resources[0]->type);
109+
110+
$this->assertLastRequestEquals('POST', '/certificates/897/actions/retry');
111+
}
97112
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"action": {
3+
"command": "retry_issuance_or_renewal",
4+
"error": {
5+
"code": "action_failed",
6+
"message": "Action failed"
7+
},
8+
"finished": "2016-01-30T23:56:00+00:00",
9+
"id": 15,
10+
"progress": 100,
11+
"resources": [
12+
{
13+
"id": 897,
14+
"type": "certificate"
15+
}
16+
],
17+
"started": "2016-01-30T23:55:00+00:00",
18+
"status": "success"
19+
}
20+
}

tests/Unit/Models/LoadBalancers/LoadBalancerTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,4 +340,14 @@ public function testUpdateService()
340340
'protocol' => 'https',
341341
]);
342342
}
343+
344+
public function testMetrics()
345+
{
346+
$this->mockHandler->append(new Response(200, [], file_get_contents(__DIR__.'/fixtures/loadBalancer_metrics.json')));
347+
$apiResponse = $this->load_balancer->metrics('open_connections', '2017-01-01T00:00:00+00:00', '2017-01-01T23:00:00+00:00', 60);
348+
$metrics = $apiResponse->getResponsePart('metrics');
349+
350+
$this->assertEquals([[1435781470.622, '42']], $metrics->time_series->open_connections->values);
351+
$this->assertLastRequestEquals('GET', '/load_balancers/4711/metrics');
352+
}
343353
}

tests/Unit/Models/LoadBalancers/LoadBalancersTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,18 @@ public function testList()
6363
$this->assertEquals($loadBalancers[0]->name, 'my-resource');
6464
$this->assertLastRequestEquals('GET', '/load_balancers');
6565
}
66+
67+
public function testCreate()
68+
{
69+
$this->mockHandler->append(new Response(201, [], file_get_contents(__DIR__.'/fixtures/loadBalancer_create.json')));
70+
$response = $this->loadBalancers->create('my-load-balancer', 'lb11', 'fsn1');
71+
72+
$this->assertNotNull($response);
73+
$this->assertEquals(4711, $response->load_balancer->id);
74+
$this->assertEquals('my-load-balancer', $response->load_balancer->name);
75+
$this->assertEquals('create_load_balancer', $response->action->command);
76+
77+
$this->assertLastRequestEquals('POST', '/load_balancers');
78+
$this->assertLastRequestBodyParametersEqual(['name' => 'my-load-balancer', 'load_balancer_type' => 'lb11', 'location' => 'fsn1']);
79+
}
6680
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{
2+
"load_balancer": {
3+
"algorithm": {
4+
"type": "round_robin"
5+
},
6+
"created": "2016-01-30T23:55:00+00:00",
7+
"id": 4711,
8+
"included_traffic": 10000,
9+
"ingoing_traffic": null,
10+
"labels": {},
11+
"load_balancer_type": {
12+
"deprecated": "2016-01-30T23:50:00+00:00",
13+
"description": "LB11",
14+
"id": 1,
15+
"max_assigned_certificates": 10,
16+
"max_connections": 20000,
17+
"max_services": 5,
18+
"max_targets": 25,
19+
"name": "lb11",
20+
"prices": [
21+
{
22+
"location": "fsn1",
23+
"price_hourly": {
24+
"gross": "1.1900000000000000",
25+
"net": "1.0000000000"
26+
},
27+
"price_monthly": {
28+
"gross": "1.1900000000000000",
29+
"net": "1.0000000000"
30+
}
31+
}
32+
]
33+
},
34+
"location": {
35+
"city": "Falkenstein",
36+
"country": "DE",
37+
"description": "Falkenstein DC Park 1",
38+
"id": 1,
39+
"latitude": 50.47612,
40+
"longitude": 12.370071,
41+
"name": "fsn1",
42+
"network_zone": "eu-central"
43+
},
44+
"name": "my-load-balancer",
45+
"outgoing_traffic": null,
46+
"private_net": [],
47+
"protection": {
48+
"delete": false
49+
},
50+
"public_net": {
51+
"enabled": true,
52+
"ipv4": {
53+
"dns_ptr": "lb1.example.com",
54+
"ip": "1.2.3.4"
55+
},
56+
"ipv6": {
57+
"dns_ptr": "lb1.example.com",
58+
"ip": "2001:db8::1"
59+
}
60+
},
61+
"services": [],
62+
"targets": []
63+
},
64+
"action": {
65+
"command": "create_load_balancer",
66+
"error": {
67+
"code": "action_failed",
68+
"message": "Action failed"
69+
},
70+
"finished": "2016-01-30T23:56:00+00:00",
71+
"id": 13,
72+
"progress": 100,
73+
"resources": [
74+
{
75+
"id": 4711,
76+
"type": "load_balancer"
77+
}
78+
],
79+
"started": "2016-01-30T23:55:00+00:00",
80+
"status": "success"
81+
}
82+
}

0 commit comments

Comments
 (0)