mirror of
https://github.com/vitodeploy/vito.git
synced 2025-04-19 01:41:36 +00:00
319 lines
9.7 KiB
PHP
Executable File
319 lines
9.7 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\ServerProviders;
|
|
|
|
use App\Exceptions\CouldNotConnectToProvider;
|
|
use App\Facades\Notifier;
|
|
use App\Notifications\FailedToDeleteServerFromProvider;
|
|
use Aws\Ec2\Ec2Client;
|
|
use Exception;
|
|
use Illuminate\Filesystem\FilesystemAdapter;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Throwable;
|
|
|
|
class AWS extends AbstractProvider
|
|
{
|
|
protected Ec2Client $ec2Client;
|
|
|
|
public function createRules(array $input): array
|
|
{
|
|
return [
|
|
'plan' => ['required'],
|
|
'region' => ['required'],
|
|
];
|
|
}
|
|
|
|
public function credentialValidationRules(array $input): array
|
|
{
|
|
return [
|
|
'key' => 'required',
|
|
'secret' => 'required',
|
|
];
|
|
}
|
|
|
|
public function credentialData(array $input): array
|
|
{
|
|
return [
|
|
'key' => $input['key'],
|
|
'secret' => $input['secret'],
|
|
];
|
|
}
|
|
|
|
public function data(array $input): array
|
|
{
|
|
return [
|
|
'plan' => $input['plan'],
|
|
'region' => $input['region'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @throws CouldNotConnectToProvider
|
|
*/
|
|
public function connect(?array $credentials = null): bool
|
|
{
|
|
try {
|
|
$this->connectToEc2ClientTest($credentials ?? []);
|
|
$this->ec2Client->describeInstances();
|
|
|
|
return true;
|
|
} catch (Exception) {
|
|
throw new CouldNotConnectToProvider('AWS');
|
|
}
|
|
}
|
|
|
|
public function plans(?string $region): array
|
|
{
|
|
$this->connectToEc2Client($region);
|
|
|
|
$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'] => __('server_providers.plan', [
|
|
'name' => $value['InstanceType'],
|
|
'cpu' => $value['VCpuInfo']['DefaultVCpus'] ?? 'N/A',
|
|
'memory' => $value['MemoryInfo']['SizeInMiB'] ?? 'N/A',
|
|
'disk' => $value['InstanceStorageInfo']['TotalSizeInGB'] ?? 'N/A',
|
|
]),
|
|
])
|
|
->toArray();
|
|
}
|
|
|
|
public function regions(): array
|
|
{
|
|
$this->connectToEc2Client();
|
|
|
|
$regions = $this->ec2Client->describeRegions();
|
|
|
|
/** @var array<int, array{RegionName: string}> $regionsArray */
|
|
$regionsArray = $regions->toArray()['Regions'] ?? [];
|
|
|
|
return collect($regionsArray)
|
|
->mapWithKeys(fn ($value) => [$value['RegionName'] => $value['RegionName']])
|
|
->toArray();
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public function create(): void
|
|
{
|
|
$this->connectToEc2Client();
|
|
$this->createKeyPair();
|
|
$this->createSecurityGroup();
|
|
$this->runInstance();
|
|
}
|
|
|
|
public function isRunning(): bool
|
|
{
|
|
$this->connectToEc2Client();
|
|
$result = $this->ec2Client->describeInstances([
|
|
'InstanceIds' => [$this->server->provider_data['instance_id']],
|
|
]);
|
|
|
|
if (count($result['Reservations'][0]['Instances']) == 1) {
|
|
if (! $this->server->ip && isset($result['Reservations'][0]['Instances'][0]['PublicIpAddress'])) {
|
|
$this->server->ip = $result['Reservations'][0]['Instances'][0]['PublicIpAddress'];
|
|
$this->server->save();
|
|
}
|
|
|
|
if (! $this->server->ip) {
|
|
return false;
|
|
}
|
|
|
|
if (isset($result['Reservations'][0]['Instances'][0]['State']) && isset($result['Reservations'][0]['Instances'][0]['State']['Name'])) {
|
|
$status = $result['Reservations'][0]['Instances'][0]['State']['Name'];
|
|
if ($status == 'running') {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function delete(): void
|
|
{
|
|
if (isset($this->server->provider_data['instance_id'])) {
|
|
try {
|
|
$this->connectToEc2Client();
|
|
$this->ec2Client->terminateInstances([
|
|
'InstanceIds' => [$this->server->provider_data['instance_id']],
|
|
]);
|
|
} catch (Throwable) {
|
|
Notifier::send($this->server, new FailedToDeleteServerFromProvider($this->server));
|
|
}
|
|
}
|
|
}
|
|
|
|
private function connectToEc2Client(?string $region = null): void
|
|
{
|
|
$credentials = $this->serverProvider->getCredentials();
|
|
|
|
if ($region === null || $region === '' || $region === '0') {
|
|
$region = $this->server->provider_data['region'] ?? null;
|
|
}
|
|
|
|
$this->ec2Client = new Ec2Client([
|
|
'region' => $region ?? config('serverproviders.aws.regions')[0]['value'],
|
|
'version' => '2016-11-15',
|
|
'credentials' => [
|
|
'key' => $credentials['key'],
|
|
'secret' => $credentials['secret'],
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $credentials
|
|
*/
|
|
private function connectToEc2ClientTest(array $credentials): void
|
|
{
|
|
$this->ec2Client = new Ec2Client([
|
|
'region' => 'us-east-1',
|
|
'version' => 'latest',
|
|
'credentials' => [
|
|
'key' => $credentials['key'],
|
|
'secret' => $credentials['secret'],
|
|
],
|
|
]);
|
|
}
|
|
|
|
private function createKeyPair(): void
|
|
{
|
|
$keyName = $this->server->name.'-'.$this->server->id;
|
|
$result = $this->ec2Client->createKeyPair([
|
|
'KeyName' => $keyName,
|
|
]);
|
|
/** @var FilesystemAdapter $storageDisk */
|
|
$storageDisk = Storage::disk(config('core.key_pairs_disk'));
|
|
$storageDisk->put((string) $this->server->id, $result['KeyMaterial']);
|
|
generate_public_key(
|
|
$storageDisk->path((string) $this->server->id),
|
|
$storageDisk->path($this->server->id.'.pub'),
|
|
);
|
|
}
|
|
|
|
private function createSecurityGroup(): void
|
|
{
|
|
$groupName = $this->server->name.'-'.$this->server->id;
|
|
$result = $this->ec2Client->createSecurityGroup([
|
|
'GroupId' => $groupName,
|
|
'GroupName' => $groupName,
|
|
'Description' => $groupName,
|
|
]);
|
|
$groupId = $result->get('GroupId');
|
|
$this->ec2Client->authorizeSecurityGroupIngress([
|
|
'GroupName' => $groupName,
|
|
'GroupId' => $groupId,
|
|
'IpPermissions' => [
|
|
[
|
|
'IpProtocol' => '-1',
|
|
'FromPort' => 0,
|
|
'ToPort' => 65535,
|
|
'IpRanges' => [
|
|
['CidrIp' => '0.0.0.0/0'],
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function runInstance(): void
|
|
{
|
|
$keyName = $groupName = $this->server->name.'-'.$this->server->id;
|
|
$result = $this->ec2Client->runInstances([
|
|
'ImageId' => $this->getImageId($this->server->os),
|
|
'MinCount' => 1,
|
|
'MaxCount' => 1,
|
|
'InstanceType' => $this->server->provider_data['plan'],
|
|
'KeyName' => $keyName,
|
|
'SecurityGroupIds' => [$groupName],
|
|
]);
|
|
$this->server->local_ip = $result['Instances'][0]['PrivateIpAddress'];
|
|
$providerData = $this->server->provider_data;
|
|
$providerData['instance_id'] = $result['Instances'][0]['InstanceId'];
|
|
$providerData['zone'] = $result['Instances'][0]['Placement']['AvailabilityZone'];
|
|
$this->server->provider_data = $providerData;
|
|
$this->server->save();
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function getImageId(string $os): string
|
|
{
|
|
$this->connectToEc2Client();
|
|
|
|
$version = config('core.operating_system_versions.'.$os);
|
|
|
|
$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');
|
|
|
|
if (! empty($images)) {
|
|
// Sort images by creation date to get the latest one
|
|
usort($images, fn (array $a, array $b): int => strtotime((string) $b['CreationDate']) - strtotime((string) $a['CreationDate']));
|
|
|
|
return $images[0]['ImageId'];
|
|
}
|
|
|
|
throw new Exception('Could not find image ID');
|
|
}
|
|
}
|