load AWS regions and plans dynamically via SDK (#366)

This commit is contained in:
Saeed Vaziry 2024-11-17 00:01:53 +01:00 committed by GitHub
parent deb0b328e3
commit cc79aa9fbf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 121 additions and 30 deletions

View File

@ -15,7 +15,6 @@
use Exception; use Exception;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
@ -45,7 +44,6 @@ public function create(User $creator, Project $project, array $input): Server
'progress_step' => 'Initializing', 'progress_step' => 'Initializing',
]); ]);
DB::beginTransaction();
try { try {
if ($server->provider != 'custom') { if ($server->provider != 'custom') {
$server->provider_id = $input['server_provider']; $server->provider_id = $input['server_provider'];
@ -70,12 +68,10 @@ public function create(User $creator, Project $project, array $input): Server
// install server // install server
$this->install($server); $this->install($server);
DB::commit();
return $server; return $server;
} catch (Exception $e) { } catch (Exception $e) {
$server->provider()->delete(); $server->delete();
DB::rollBack();
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'provider' => $e->getMessage(), 'provider' => $e->getMessage(),
]); ]);

View File

@ -79,6 +79,7 @@ public static function regions(?int $id): array
if (! $id) { if (! $id) {
return []; return [];
} }
/** @var ?ServerProvider $profile */
$profile = self::find($id); $profile = self::find($id);
if (! $profile) { if (! $profile) {
return []; return [];
@ -109,7 +110,7 @@ public static function plans(?int $id, ?string $region): array
} }
$plans = $profile->provider()->plans($region); $plans = $profile->provider()->plans($region);
Cache::put('plans-'.$id.'-'.$region, $plans, 600); Cache::put('plans-'.$id.'-'.$region, $plans, 60);
return $plans; return $plans;
} }

View File

@ -20,21 +20,10 @@ class AWS extends AbstractProvider
public function createRules(array $input): array public function createRules(array $input): array
{ {
$rules = []; return [
// plans 'plan' => ['required'],
$plans = []; 'region' => ['required'],
foreach (config('serverproviders.aws.plans') as $plan) { ];
$plans[] = $plan['value'];
}
$rules['plan'] = 'required|in:'.implode(',', $plans);
// regions
$regions = [];
foreach (config('serverproviders.aws.regions') as $region) {
$regions[] = $region['value'];
}
$rules['region'] = 'required|in:'.implode(',', $regions);
return $rules;
} }
public function credentialValidationRules(array $input): array public function credentialValidationRules(array $input): array
@ -78,18 +67,65 @@ public function connect(?array $credentials = null): bool
public function plans(?string $region): array public function plans(?string $region): array
{ {
return collect(config('serverproviders.aws.plans')) $this->connectToEc2Client($region);
->mapWithKeys(fn ($value) => [$value['value'] => $value['title']])
$nextToken = null;
$plans = [];
do {
$params = [
'Filters' => [
[
'Name' => 'processor-info.supported-architecture',
'Values' => ['x86_64', 'arm64'], // Include both x86_64 and ARM64
],
[
'Name' => 'current-generation',
'Values' => ['true'],
],
[
'Name' => 'supported-virtualization-type',
'Values' => ['hvm'], // Ubuntu AMIs require HVM
],
[
'Name' => 'bare-metal',
'Values' => ['false'], // Skip bare-metal unless explicitly needed
],
],
];
if ($nextToken) {
$params['NextToken'] = $nextToken;
}
$result = $this->ec2Client->describeInstanceTypes($params);
$plans = array_merge($plans, $result->get('InstanceTypes'));
$nextToken = $result->get('NextToken');
} while ($nextToken);
return collect($plans)
->mapWithKeys(fn ($value) => [
$value['InstanceType'] => $value['InstanceType'].' - '.$value['VCpuInfo']['DefaultVCpus'].' vCPUs, '.$value['MemoryInfo']['SizeInMiB'].' MB RAM',
])
->toArray(); ->toArray();
} }
public function regions(): array public function regions(): array
{ {
return collect(config('serverproviders.aws.regions')) $this->connectToEc2Client();
->mapWithKeys(fn ($value) => [$value['value'] => $value['title']])
$regions = $this->ec2Client->describeRegions();
return collect($regions->toArray()['Regions'] ?? [])
->mapWithKeys(fn ($value) => [$value['RegionName'] => $value['RegionName']])
->toArray(); ->toArray();
} }
/**
* @throws Exception
*/
public function create(): void public function create(): void
{ {
$this->connectToEc2Client(); $this->connectToEc2Client();
@ -136,12 +172,16 @@ public function delete(): void
} }
} }
private function connectToEc2Client(): void private function connectToEc2Client(?string $region = null): void
{ {
$credentials = $this->server->serverProvider->getCredentials(); $credentials = $this->serverProvider->getCredentials();
if (! $region) {
$region = $this->server?->provider_data['region'];
}
$this->ec2Client = new Ec2Client([ $this->ec2Client = new Ec2Client([
'region' => $this->server->provider_data['region'], 'region' => $region ?? config('serverproviders.aws.regions')[0]['value'],
'version' => '2016-11-15', 'version' => '2016-11-15',
'credentials' => [ 'credentials' => [
'key' => $credentials['key'], 'key' => $credentials['key'],
@ -202,11 +242,14 @@ private function createSecurityGroup(): void
]); ]);
} }
/**
* @throws Exception
*/
private function runInstance(): void private function runInstance(): void
{ {
$keyName = $groupName = $this->server->name.'-'.$this->server->id; $keyName = $groupName = $this->server->name.'-'.$this->server->id;
$result = $this->ec2Client->runInstances([ $result = $this->ec2Client->runInstances([
'ImageId' => config('serverproviders.aws.images.'.$this->server->provider_data['region'].'.'.$this->server->os), 'ImageId' => $this->getImageId($this->server->os),
'MinCount' => 1, 'MinCount' => 1,
'MaxCount' => 1, 'MaxCount' => 1,
'InstanceType' => $this->server->provider_data['plan'], 'InstanceType' => $this->server->provider_data['plan'],
@ -220,4 +263,50 @@ private function runInstance(): void
$this->server->provider_data = $providerData; $this->server->provider_data = $providerData;
$this->server->save(); $this->server->save();
} }
/**
* @throws Exception
*/
public function getImageId(string $os): string
{
$this->connectToEc2Client();
$version = config('core.operating_system_versions.'.$os);
ds($version);
$result = $this->ec2Client->describeImages([
'Filters' => [
[
'Name' => 'name',
'Values' => ['ubuntu/images/*-'.$version.'-amd64-server-*'],
],
[
'Name' => 'state',
'Values' => ['available'],
],
[
'Name' => 'virtualization-type',
'Values' => ['hvm'],
],
],
'Owners' => ['099720109477'],
]);
// Extract and display image information
$images = $result->get('Images');
ds($images);
if (! empty($images)) {
// Sort images by creation date to get the latest one
usort($images, function ($a, $b) {
return strtotime($b['CreationDate']) - strtotime($a['CreationDate']);
});
return $images[0]['ImageId'];
}
throw new Exception('Could not find image ID');
}
} }

View File

@ -18,6 +18,11 @@
\App\Enums\OperatingSystem::UBUNTU22, \App\Enums\OperatingSystem::UBUNTU22,
\App\Enums\OperatingSystem::UBUNTU24, \App\Enums\OperatingSystem::UBUNTU24,
], ],
'operating_system_versions' => [
\App\Enums\OperatingSystem::UBUNTU20 => '20.04',
\App\Enums\OperatingSystem::UBUNTU22 => '22.04',
\App\Enums\OperatingSystem::UBUNTU24 => '24.04',
],
'webservers' => [ 'webservers' => [
\App\Enums\Webserver::NONE, \App\Enums\Webserver::NONE,
\App\Enums\Webserver::NGINX, \App\Enums\Webserver::NGINX,