load Vultr regions and plans dynamically (#369)

This commit is contained in:
Saeed Vaziry 2024-11-17 12:09:41 +01:00 committed by GitHub
parent 57b2771c7e
commit 20944421de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 144 additions and 56 deletions

View File

@ -6,7 +6,6 @@
use App\Facades\Notifier; use App\Facades\Notifier;
use App\Notifications\FailedToDeleteServerFromProvider; use App\Notifications\FailedToDeleteServerFromProvider;
use Aws\Ec2\Ec2Client; use Aws\Ec2\Ec2Client;
use Aws\EC2InstanceConnect\EC2InstanceConnectClient;
use Exception; use Exception;
use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -16,8 +15,6 @@ class AWS extends AbstractProvider
{ {
protected Ec2Client $ec2Client; protected Ec2Client $ec2Client;
protected EC2InstanceConnectClient $ec2InstanceConnectClient;
public function createRules(array $input): array public function createRules(array $input): array
{ {
return [ return [
@ -272,14 +269,12 @@ private function runInstance(): void
/** /**
* @throws Exception * @throws Exception
*/ */
public function getImageId(string $os): string private function getImageId(string $os): string
{ {
$this->connectToEc2Client(); $this->connectToEc2Client();
$version = config('core.operating_system_versions.'.$os); $version = config('core.operating_system_versions.'.$os);
ds($version);
$result = $this->ec2Client->describeImages([ $result = $this->ec2Client->describeImages([
'Filters' => [ 'Filters' => [
[ [
@ -301,8 +296,6 @@ public function getImageId(string $os): string
// Extract and display image information // Extract and display image information
$images = $result->get('Images'); $images = $result->get('Images');
ds($images);
if (! empty($images)) { if (! empty($images)) {
// Sort images by creation date to get the latest one // Sort images by creation date to get the latest one
usort($images, function ($a, $b) { usort($images, function ($a, $b) {

View File

@ -3,6 +3,7 @@
namespace App\ServerProviders; namespace App\ServerProviders;
use App\ValidationRules\RestrictedIPAddressesRule; use App\ValidationRules\RestrictedIPAddressesRule;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
@ -58,7 +59,7 @@ public function regions(): array
public function create(): void public function create(): void
{ {
/** @var \Illuminate\Filesystem\FilesystemAdapter $storageDisk */ /** @var FilesystemAdapter $storageDisk */
$storageDisk = Storage::disk(config('core.key_pairs_disk')); $storageDisk = Storage::disk(config('core.key_pairs_disk'));
File::copy( File::copy(
storage_path(config('core.ssh_private_key_name')), storage_path(config('core.ssh_private_key_name')),

View File

@ -69,8 +69,6 @@ public function plans(?string $region): array
->get($this->apiUrl.'/sizes', ['per_page' => 200]) ->get($this->apiUrl.'/sizes', ['per_page' => 200])
->json(); ->json();
ds($region);
return collect($plans['sizes'])->filter(function ($size) use ($region) { return collect($plans['sizes'])->filter(function ($size) use ($region) {
return in_array($region, $size['regions']); return in_array($region, $size['regions']);
}) })
@ -133,7 +131,7 @@ public function create(): void
'name' => str($this->server->name)->slug(), 'name' => str($this->server->name)->slug(),
'region' => $this->server->provider_data['region'], 'region' => $this->server->provider_data['region'],
'size' => $this->server->provider_data['plan'], 'size' => $this->server->provider_data['plan'],
'image' => config('serverproviders.digitalocean.images')[$this->server->os], 'image' => $this->getImageId($this->server->os, $this->server->provider_data['region']),
'backups' => false, 'backups' => false,
'ipv6' => false, 'ipv6' => false,
'monitoring' => false, 'monitoring' => false,
@ -195,4 +193,33 @@ public function delete(): void
} }
} }
} }
/**
* @throws Exception
*/
private function getImageId(string $os, string $region): int
{
$version = config('core.operating_system_versions.'.$os);
try {
$result = Http::withToken($this->serverProvider->credentials['token'])
->get($this->apiUrl.'/images', [
'per_page' => 200,
'type' => 'distribution',
])
->json();
$image = collect($result['images'])
->filter(function ($image) use ($region, $version) {
return in_array($region, $image['regions']) && str_contains($image['name'], $version);
})
->where('distribution', 'Ubuntu')
->where('status', 'available')
->first();
return $image['id'];
} catch (Exception) {
throw new Exception('Could not find image ID');
}
}
} }

View File

@ -7,6 +7,7 @@
use App\Facades\Notifier; use App\Facades\Notifier;
use App\Notifications\FailedToDeleteServerFromProvider; use App\Notifications\FailedToDeleteServerFromProvider;
use Exception; use Exception;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -15,23 +16,12 @@ class Vultr extends AbstractProvider
{ {
protected string $apiUrl = 'https://api.vultr.com/v2'; protected string $apiUrl = 'https://api.vultr.com/v2';
public function createRules($input): array public function createRules(array $input): array
{ {
$rules = []; return [
// plans 'plan' => 'required',
$plans = []; 'region' => 'required',
foreach (config('serverproviders.vultr.plans') as $plan) { ];
$plans[] = $plan['value'];
}
$rules['plan'] = 'required|in:'.implode(',', $plans);
// regions
$regions = [];
foreach (config('serverproviders.vultr.regions') as $region) {
$regions[] = $region['value'];
}
$rules['region'] = 'required|in:'.implode(',', $regions);
return $rules;
} }
public function credentialValidationRules($input): array public function credentialValidationRules($input): array
@ -61,7 +51,12 @@ public function data(array $input): array
*/ */
public function connect(?array $credentials = null): bool public function connect(?array $credentials = null): bool
{ {
$connect = Http::withToken($credentials['token'])->get($this->apiUrl.'/account'); try {
$connect = Http::withToken($credentials['token'])->get($this->apiUrl.'/account');
} catch (Exception) {
throw new CouldNotConnectToProvider('Vultr');
}
if (! $connect->ok()) { if (! $connect->ok()) {
throw new CouldNotConnectToProvider('Vultr'); throw new CouldNotConnectToProvider('Vultr');
} }
@ -71,16 +66,43 @@ public function connect(?array $credentials = null): bool
public function plans(?string $region): array public function plans(?string $region): array
{ {
return collect(config('serverproviders.vultr.plans')) try {
->mapWithKeys(fn ($value) => [$value['value'] => $value['title']]) $plans = Http::withToken($this->serverProvider->credentials['token'])
->toArray(); ->get($this->apiUrl.'/plans', ['per_page' => 500])
->json();
return collect($plans['plans'])->filter(function ($plan) use ($region) {
return in_array($region, $plan['locations']);
})
->mapWithKeys(function ($value) {
return [
$value['id'] => __('server_providers.plan', [
'name' => $value['type'],
'cpu' => $value['vcpu_count'],
'memory' => $value['ram'],
'disk' => $value['disk'],
]),
];
})
->toArray();
} catch (Exception) {
return [];
}
} }
public function regions(): array public function regions(): array
{ {
return collect(config('serverproviders.vultr.regions')) try {
->mapWithKeys(fn ($value) => [$value['value'] => $value['title']]) $regions = Http::withToken($this->serverProvider->credentials['token'])
->toArray(); ->get($this->apiUrl.'/regions', ['per_page' => 500])
->json();
return collect($regions['regions'])
->mapWithKeys(fn ($value) => [$value['id'] => $value['country'].' - '.$value['city']])
->toArray();
} catch (Exception) {
return [];
}
} }
/** /**
@ -89,32 +111,41 @@ public function regions(): array
public function create(): void public function create(): void
{ {
// generate key pair // generate key pair
/** @var \Illuminate\Filesystem\FilesystemAdapter $storageDisk */ /** @var FilesystemAdapter $storageDisk */
$storageDisk = Storage::disk(config('core.key_pairs_disk')); $storageDisk = Storage::disk(config('core.key_pairs_disk'));
generate_key_pair($storageDisk->path((string) $this->server->id)); generate_key_pair($storageDisk->path((string) $this->server->id));
$createSshKey = Http::withToken($this->server->serverProvider->credentials['token']) try {
->post($this->apiUrl.'/ssh-keys', [ $createSshKey = Http::withToken($this->server->serverProvider->credentials['token'])
'ssh_key' => $this->server->sshKey()['public_key'], ->post($this->apiUrl.'/ssh-keys', [
'name' => $this->server->name.'_'.$this->server->id, 'ssh_key' => $this->server->sshKey()['public_key'],
]); 'name' => $this->server->name.'_'.$this->server->id,
]);
} catch (Exception) {
throw new ServerProviderError('Error creating SSH Key on Vultr');
}
if ($createSshKey->status() != 201) { if ($createSshKey->status() != 201) {
throw new ServerProviderError('Error creating SSH Key on Vultr'); throw new ServerProviderError('Error creating SSH Key on Vultr');
} }
$create = Http::withToken($this->server->serverProvider->credentials['token']) try {
->post($this->apiUrl.'/instances', [ $create = Http::withToken($this->server->serverProvider->credentials['token'])
'label' => $this->server->name, ->post($this->apiUrl.'/instances', [
'region' => $this->server->provider_data['region'], 'label' => $this->server->name,
'plan' => $this->server->provider_data['plan'], 'region' => $this->server->provider_data['region'],
'os_id' => config('serverproviders.vultr.images')[$this->server->os], 'plan' => $this->server->provider_data['plan'],
'enable_ipv6' => false, 'os_id' => $this->getImageId($this->server->os),
'sshkey_id' => [$createSshKey->json()['ssh_key']['id']], 'enable_ipv6' => false,
]); 'sshkey_id' => [$createSshKey->json()['ssh_key']['id']],
]);
} catch (Exception) {
throw new ServerProviderError('Failed to create server on Vultr');
}
if ($create->status() != 202) { if ($create->status() != 202) {
$msg = __('Failed to create server on Vultr');
Log::error('Failed to create server on Vultr', $create->json()); Log::error('Failed to create server on Vultr', $create->json());
throw new ServerProviderError($msg); throw new ServerProviderError('Failed: '.$create->body());
} }
$providerData = $this->server->provider_data; $providerData = $this->server->provider_data;
$providerData['instance_id'] = $create->json()['instance']['id']; $providerData['instance_id'] = $create->json()['instance']['id'];
@ -124,8 +155,12 @@ public function create(): void
public function isRunning(): bool public function isRunning(): bool
{ {
$status = Http::withToken($this->server->serverProvider->credentials['token']) try {
->get($this->apiUrl.'/instances/'.$this->server->provider_data['instance_id']); $status = Http::withToken($this->server->serverProvider->credentials['token'])
->get($this->apiUrl.'/instances/'.$this->server->provider_data['instance_id']);
} catch (Exception) {
return false;
}
if (! $status->ok()) { if (! $status->ok()) {
return false; return false;
@ -145,12 +180,44 @@ public function isRunning(): bool
public function delete(): void public function delete(): void
{ {
if (isset($this->server->provider_data['instance_id'])) { if (isset($this->server->provider_data['instance_id'])) {
$delete = Http::withToken($this->server->serverProvider->credentials['token']) try {
->delete($this->apiUrl.'/instances/'.$this->server->provider_data['instance_id']); $delete = Http::withToken($this->server->serverProvider->credentials['token'])
->delete($this->apiUrl.'/instances/'.$this->server->provider_data['instance_id']);
} catch (Exception) {
Notifier::send($this->server, new FailedToDeleteServerFromProvider($this->server));
return;
}
if (! $delete->ok()) { if (! $delete->ok()) {
Notifier::send($this->server, new FailedToDeleteServerFromProvider($this->server)); Notifier::send($this->server, new FailedToDeleteServerFromProvider($this->server));
} }
} }
} }
/**
* @throws Exception
*/
private function getImageId(string $os): int
{
$version = config('core.operating_system_versions.'.$os);
try {
$result = Http::withToken($this->serverProvider->credentials['token'])
->get($this->apiUrl.'/os', ['per_page' => 500])
->json();
$image = collect($result['os'])
->filter(function ($os) use ($version) {
return str_contains($os['name'], $version);
})
->where('family', 'ubuntu')
->where('arch', 'x64')
->first();
return $image['id'];
} catch (Exception) {
throw new Exception('Could not find image ID');
}
}
} }