Skip to content

Commit 5936874

Browse files
authored
feat(vultr): add cloud provider integration (#10533)
2 parents b0f0f7d + 2dd264f commit 5936874

57 files changed

Lines changed: 3482 additions & 97 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/Actions/Server/DeleteServer.php

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
use App\Models\Team;
88
use App\Notifications\Server\HetznerDeletionFailed;
99
use App\Services\HetznerService;
10+
use App\Services\VultrService;
1011
use Lorisleiva\Actions\Concerns\AsAction;
1112

1213
class DeleteServer
1314
{
1415
use AsAction;
1516

16-
public function handle(int $serverId, bool $deleteFromHetzner = false, ?int $hetznerServerId = null, ?int $cloudProviderTokenId = null, ?int $teamId = null)
17+
public function handle(int $serverId, bool $deleteFromHetzner = false, ?int $hetznerServerId = null, ?int $cloudProviderTokenId = null, ?int $teamId = null, bool $deleteFromVultr = false, ?string $vultrInstanceId = null)
1718
{
1819
$server = Server::withTrashed()->find($serverId);
1920

@@ -26,6 +27,16 @@ public function handle(int $serverId, bool $deleteFromHetzner = false, ?int $het
2627
);
2728
}
2829

30+
if ($deleteFromVultr && ($vultrInstanceId || ($server && $server->vultr_instance_id))) {
31+
$this->deleteFromVultrById(
32+
$vultrInstanceId ?? $server->vultr_instance_id,
33+
$cloudProviderTokenId ?? $server->cloud_provider_token_id,
34+
$teamId ?? $server->team_id
35+
);
36+
}
37+
38+
logger()->debug($server ? 'Deleting server from Coolify' : 'Server already deleted from Coolify, skipping Coolify deletion');
39+
2940
// If server is already deleted from Coolify, skip this part
3041
if (! $server) {
3142
return; // Server already force deleted from Coolify
@@ -48,7 +59,10 @@ private function deleteFromHetznerById(int $hetznerServerId, ?int $cloudProvider
4859
$token = null;
4960

5061
if ($cloudProviderTokenId) {
51-
$token = CloudProviderToken::find($cloudProviderTokenId);
62+
$token = CloudProviderToken::where('id', $cloudProviderTokenId)
63+
->where('team_id', $teamId)
64+
->where('provider', 'hetzner')
65+
->first();
5266
}
5367

5468
if (! $token) {
@@ -79,4 +93,47 @@ private function deleteFromHetznerById(int $hetznerServerId, ?int $cloudProvider
7993
$team?->notify(new HetznerDeletionFailed($hetznerServerId, $teamId, $e->getMessage()));
8094
}
8195
}
96+
97+
private function deleteFromVultrById(string $vultrInstanceId, ?int $cloudProviderTokenId, int $teamId): void
98+
{
99+
try {
100+
$token = null;
101+
102+
if ($cloudProviderTokenId) {
103+
$token = CloudProviderToken::where('id', $cloudProviderTokenId)
104+
->where('team_id', $teamId)
105+
->where('provider', 'vultr')
106+
->first();
107+
}
108+
109+
if (! $token) {
110+
$token = CloudProviderToken::where('team_id', $teamId)
111+
->where('provider', 'vultr')
112+
->first();
113+
}
114+
115+
if (! $token) {
116+
logger()->debug('No Vultr token found for team, skipping Vultr deletion', [
117+
'team_id' => $teamId,
118+
'vultr_instance_id' => $vultrInstanceId,
119+
]);
120+
121+
return;
122+
}
123+
124+
$vultrService = new VultrService($token->token);
125+
$vultrService->deleteInstance($vultrInstanceId);
126+
127+
logger()->debug('Deleted server from Vultr', [
128+
'vultr_instance_id' => $vultrInstanceId,
129+
'team_id' => $teamId,
130+
]);
131+
} catch (\Throwable $e) {
132+
logger()->error('Failed to delete server from Vultr', [
133+
'error' => $e->getMessage(),
134+
'vultr_instance_id' => $vultrInstanceId,
135+
'team_id' => $teamId,
136+
]);
137+
}
138+
}
82139
}

app/Actions/Server/ValidateServer.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ public function handle(Server $server)
2828
$server->update([
2929
'validation_logs' => null,
3030
]);
31+
if ($server->vultr_instance_id) {
32+
$status = $server->refreshVultrState();
33+
if (in_array($status, ['stopped', 'suspended', 'deleted'], true)) {
34+
$this->error = $status === 'deleted'
35+
? 'Vultr instance is deleted or no longer accessible. Relink this server before validating.'
36+
: 'Vultr instance is '.($status ?? 'not running').'. Power it on before validating.';
37+
$server->update([
38+
'validation_logs' => $this->error,
39+
]);
40+
throw new \Exception($this->error);
41+
}
42+
}
43+
3144
['uptime' => $this->uptime, 'error' => $error] = $server->validateConnection();
3245
if (! $this->uptime) {
3346
$sanitizedError = htmlspecialchars($error ?? '', ENT_QUOTES, 'UTF-8');

app/Http/Controllers/Api/CloudProviderTokensController.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ private function validateProviderToken(string $provider, string $token): array
4242
'digitalocean' => Http::withHeaders([
4343
'Authorization' => 'Bearer '.$token,
4444
])->timeout(10)->get('https://api.digitalocean.com/v2/account'),
45+
'vultr' => Http::withHeaders([
46+
'Authorization' => 'Bearer '.$token,
47+
])->timeout(10)->get('https://api.vultr.com/v2/account'),
4548
default => null,
4649
};
4750

@@ -87,7 +90,7 @@ private function validateProviderToken(string $provider, string $token): array
8790
properties: [
8891
'uuid' => ['type' => 'string'],
8992
'name' => ['type' => 'string'],
90-
'provider' => ['type' => 'string', 'enum' => ['hetzner', 'digitalocean']],
93+
'provider' => ['type' => 'string', 'enum' => ['hetzner', 'digitalocean', 'vultr']],
9194
'team_id' => ['type' => 'integer'],
9295
'servers_count' => ['type' => 'integer'],
9396
'created_at' => ['type' => 'string'],
@@ -205,7 +208,7 @@ public function show(Request $request)
205208
type: 'object',
206209
required: ['provider', 'token', 'name'],
207210
properties: [
208-
'provider' => ['type' => 'string', 'enum' => ['hetzner', 'digitalocean'], 'example' => 'hetzner', 'description' => 'The cloud provider.'],
211+
'provider' => ['type' => 'string', 'enum' => ['hetzner', 'digitalocean', 'vultr'], 'example' => 'hetzner', 'description' => 'The cloud provider.'],
209212
'token' => ['type' => 'string', 'example' => 'your-api-token-here', 'description' => 'The API token for the cloud provider.'],
210213
'name' => ['type' => 'string', 'example' => 'My Hetzner Token', 'description' => 'A friendly name for the token.'],
211214
],
@@ -260,7 +263,7 @@ public function store(Request $request)
260263
$body = $request->json()->all();
261264

262265
$validator = customApiValidator($body, [
263-
'provider' => 'required|string|in:hetzner,digitalocean',
266+
'provider' => 'required|string|in:hetzner,digitalocean,vultr',
264267
'token' => 'required|string',
265268
'name' => 'required|string|max:255',
266269
]);

app/Http/Controllers/Api/ServersController.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,9 @@ public function delete_server(Request $request)
862862
false, // Don't delete from Hetzner via API
863863
$server->hetzner_server_id,
864864
$server->cloud_provider_token_id,
865-
$server->team_id
865+
$server->team_id,
866+
false, // Don't delete from Vultr via API
867+
$server->vultr_instance_id
866868
);
867869

868870
auditLog('api.server.deleted', [

0 commit comments

Comments
 (0)