mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-02 14:36:17 +00:00
init
This commit is contained in:
223
app/ServerProviders/AWS.php
Executable file
223
app/ServerProviders/AWS.php
Executable file
@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
namespace App\ServerProviders;
|
||||
|
||||
use App\Enums\OperatingSystem;
|
||||
use App\Exceptions\CouldNotConnectToProvider;
|
||||
use App\Notifications\FailedToDeleteServerFromProvider;
|
||||
use Aws\Ec2\Ec2Client;
|
||||
use Aws\EC2InstanceConnect\EC2InstanceConnectClient;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class AWS extends AbstractProvider
|
||||
{
|
||||
protected Ec2Client $ec2Client;
|
||||
|
||||
protected EC2InstanceConnectClient $ec2InstanceConnectClient;
|
||||
|
||||
public function createValidationRules(array $input): array
|
||||
{
|
||||
$rules = [
|
||||
'size' => 'required|numeric|min:15|max:16000',
|
||||
'os' => 'required|in:'.implode(',', OperatingSystem::getValues()),
|
||||
];
|
||||
// plans
|
||||
$plans = [];
|
||||
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
|
||||
{
|
||||
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'],
|
||||
'size' => $input['size'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(): array
|
||||
{
|
||||
return config('serverproviders.aws.plans');
|
||||
}
|
||||
|
||||
public function regions(): array
|
||||
{
|
||||
return config('serverproviders.aws.regions');
|
||||
}
|
||||
|
||||
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 (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;
|
||||
}
|
||||
}
|
||||
|
||||
Log::info($result);
|
||||
}
|
||||
|
||||
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 (Exception) {
|
||||
/** @todo notify */
|
||||
// $this->server->team->notify(new FailedToDeleteServerFromProvider($this->server));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function connectToEc2Client(): void
|
||||
{
|
||||
$credentials = $this->server->serverProvider->getCredentials();
|
||||
|
||||
$this->ec2Client = new Ec2Client([
|
||||
'region' => $this->server->provider_data['region'],
|
||||
'version' => '2016-11-15',
|
||||
'credentials' => [
|
||||
'key' => $credentials['key'],
|
||||
'secret' => $credentials['secret'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function connectToEc2ClientTest($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,
|
||||
]);
|
||||
Storage::disk(config('core.key_pairs_disk'))->put((string) $this->server->id, $result['KeyMaterial']);
|
||||
generate_public_key(
|
||||
Storage::disk(config('core.key_pairs_disk'))->path((string) $this->server->id),
|
||||
Storage::disk(config('core.key_pairs_disk'))->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'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function runInstance(): void
|
||||
{
|
||||
$keyName = $groupName = $this->server->name.'-'.$this->server->id;
|
||||
$result = $this->ec2Client->runInstances([
|
||||
'ImageId' => config('serverproviders.aws.images.'.$this->server->provider_data['region'].'.'.$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();
|
||||
}
|
||||
}
|
22
app/ServerProviders/AbstractProvider.php
Executable file
22
app/ServerProviders/AbstractProvider.php
Executable file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\ServerProviders;
|
||||
|
||||
use App\Contracts\ServerProvider;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
abstract class AbstractProvider implements ServerProvider
|
||||
{
|
||||
protected ?Server $server;
|
||||
|
||||
public function __construct(Server $server = null)
|
||||
{
|
||||
$this->server = $server;
|
||||
}
|
||||
|
||||
protected function generateKeyPair(): void
|
||||
{
|
||||
generate_key_pair(Storage::disk(config('core.key_pairs_disk'))->path((string) $this->server->id));
|
||||
}
|
||||
}
|
72
app/ServerProviders/Custom.php
Executable file
72
app/ServerProviders/Custom.php
Executable file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\ServerProviders;
|
||||
|
||||
use App\ValidationRules\RestrictedIPAddressesRule;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class Custom extends AbstractProvider
|
||||
{
|
||||
public function createValidationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'ip' => [
|
||||
'required',
|
||||
'ip',
|
||||
Rule::unique('servers', 'ip'),
|
||||
new RestrictedIPAddressesRule(),
|
||||
],
|
||||
'port' => [
|
||||
'required',
|
||||
'numeric',
|
||||
'min:1',
|
||||
'max:65535',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function credentialValidationRules(array $input): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function credentialData(array $input): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function data(array $input): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function connect(array $credentials = null): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function plans(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function regions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
$this->generateKeyPair();
|
||||
}
|
||||
|
||||
public function isRunning(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
158
app/ServerProviders/DigitalOcean.php
Normal file
158
app/ServerProviders/DigitalOcean.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace App\ServerProviders;
|
||||
|
||||
use App\Exceptions\CouldNotConnectToProvider;
|
||||
use App\Exceptions\ServerProviderError;
|
||||
use App\Notifications\FailedToDeleteServerFromProvider;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DigitalOcean extends AbstractProvider
|
||||
{
|
||||
protected string $apiUrl = 'https://api.digitalocean.com/v2';
|
||||
|
||||
public function createValidationRules(array $input): array
|
||||
{
|
||||
$rules = [
|
||||
'os' => 'required|in:'.implode(',', config('core.operating_systems')),
|
||||
];
|
||||
// plans
|
||||
$plans = [];
|
||||
foreach (config('serverproviders.digitalocean.plans') as $plan) {
|
||||
$plans[] = $plan['value'];
|
||||
}
|
||||
$rules['plan'] = 'required|in:'.implode(',', $plans);
|
||||
// regions
|
||||
$regions = [];
|
||||
foreach (config('serverproviders.digitalocean.regions') as $region) {
|
||||
$regions[] = $region['value'];
|
||||
}
|
||||
$rules['region'] = 'required|in:'.implode(',', $regions);
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function credentialValidationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'token' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
public function credentialData(array $input): array
|
||||
{
|
||||
return [
|
||||
'token' => $input['token'],
|
||||
];
|
||||
}
|
||||
|
||||
public function data(array $input): array
|
||||
{
|
||||
return [
|
||||
'plan' => $input['plan'],
|
||||
'region' => $input['region'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CouldNotConnectToProvider
|
||||
*/
|
||||
public function connect(array $credentials = null): bool
|
||||
{
|
||||
$connect = Http::withToken($credentials['token'])->get($this->apiUrl.'/account');
|
||||
if (! $connect->ok()) {
|
||||
throw new CouldNotConnectToProvider('DigitalOcean');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function plans(): array
|
||||
{
|
||||
return config('serverproviders.digitalocean.plans');
|
||||
}
|
||||
|
||||
public function regions(): array
|
||||
{
|
||||
return config('serverproviders.digitalocean.regions');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ServerProviderError
|
||||
*/
|
||||
public function create(): void
|
||||
{
|
||||
$this->generateKeyPair();
|
||||
|
||||
$createSshKey = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->post($this->apiUrl.'/account/keys', [
|
||||
'public_key' => $this->server->sshKey()['public_key'],
|
||||
'name' => str($this->server->name)->slug().'-'.$this->server->id,
|
||||
]);
|
||||
if ($createSshKey->status() != 201) {
|
||||
throw new ServerProviderError('DigitalOcean SSH Key');
|
||||
}
|
||||
|
||||
$create = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->post($this->apiUrl.'/droplets', [
|
||||
'name' => str($this->server->name)->slug(),
|
||||
'region' => $this->server->provider_data['region'],
|
||||
'size' => $this->server->provider_data['plan'],
|
||||
'image' => config('serverproviders.digitalocean.images')[$this->server->os],
|
||||
'backups' => false,
|
||||
'ipv6' => false,
|
||||
'monitoring' => false,
|
||||
'ssh_keys' => [$createSshKey->json()['ssh_key']['id']],
|
||||
]);
|
||||
if ($create->status() != 202) {
|
||||
$msg = __('Failed to create server on DigitalOcean');
|
||||
Log::error('Failed to create server on DigitalOcean', $create->json());
|
||||
throw new ServerProviderError($msg);
|
||||
}
|
||||
$providerData = $this->server->provider_data;
|
||||
$providerData['droplet_id'] = $create->json()['droplet']['id'];
|
||||
$this->server->provider_data = $providerData;
|
||||
$this->server->save();
|
||||
}
|
||||
|
||||
public function isRunning(): bool
|
||||
{
|
||||
$status = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->get($this->apiUrl.'/droplets/'.$this->server->provider_data['droplet_id']);
|
||||
|
||||
if (! $status->ok()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->server->ip && count($status->json()['droplet']['networks']['v4']) > 0) {
|
||||
foreach ($status->json()['droplet']['networks']['v4'] as $v4) {
|
||||
if ($v4['type'] == 'public') {
|
||||
$this->server->ip = $v4['ip_address'];
|
||||
} else {
|
||||
$this->server->local_ip = $v4['ip_address'];
|
||||
}
|
||||
}
|
||||
$this->server->save();
|
||||
}
|
||||
|
||||
return $status->json()['droplet']['status'] == 'active';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
if (isset($this->server->provider_data['droplet_id'])) {
|
||||
$delete = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->delete($this->apiUrl.'/droplets/'.$this->server->provider_data['droplet_id']);
|
||||
|
||||
/** @todo notify */
|
||||
// if (! $delete->ok()) {
|
||||
// $this->server->team->notify(new FailedToDeleteServerFromProvider($this->server));
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
146
app/ServerProviders/Hetzner.php
Normal file
146
app/ServerProviders/Hetzner.php
Normal file
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace App\ServerProviders;
|
||||
|
||||
use App\Exceptions\CouldNotConnectToProvider;
|
||||
use App\Exceptions\ServerProviderError;
|
||||
use App\Notifications\FailedToDeleteServerFromProvider;
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class Hetzner extends AbstractProvider
|
||||
{
|
||||
protected string $apiUrl = 'https://api.hetzner.cloud/v1';
|
||||
|
||||
public function createValidationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'os' => 'required|in:'.implode(',', config('core.operating_systems')),
|
||||
'plan' => 'required',
|
||||
'region' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
public function credentialValidationRules(array $input): array
|
||||
{
|
||||
return [
|
||||
'token' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
public function credentialData(array $input): array
|
||||
{
|
||||
return [
|
||||
'token' => $input['token'],
|
||||
];
|
||||
}
|
||||
|
||||
public function data(array $input): array
|
||||
{
|
||||
return [
|
||||
'plan' => $input['plan'],
|
||||
'region' => $input['region'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CouldNotConnectToProvider
|
||||
*/
|
||||
public function connect(array $credentials = null): bool
|
||||
{
|
||||
$connect = Http::withToken($credentials['token'])->get($this->apiUrl.'/servers');
|
||||
if (! $connect->ok()) {
|
||||
throw new CouldNotConnectToProvider('Hetzner');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function plans(): array
|
||||
{
|
||||
return config('serverproviders.hetzner.plans');
|
||||
}
|
||||
|
||||
public function regions(): array
|
||||
{
|
||||
return config('serverproviders.hetzner.regions');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ServerProviderError
|
||||
*/
|
||||
public function create(): void
|
||||
{
|
||||
$this->generateKeyPair();
|
||||
|
||||
$sshKey = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->post($this->apiUrl.'/ssh_keys', [
|
||||
'name' => 'server-'.$this->server->id.'-key',
|
||||
'public_key' => $this->server->sshKey()['public_key'],
|
||||
]);
|
||||
|
||||
if ($sshKey->status() != 201) {
|
||||
$this->providerError($sshKey);
|
||||
}
|
||||
|
||||
$this->server->jsonUpdate('provider_data', 'ssh_key_id', $sshKey->json()['ssh_key']['id']);
|
||||
|
||||
$create = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->post($this->apiUrl.'/servers', [
|
||||
'automount' => false,
|
||||
'image' => config('serverproviders.hetzner.images')[$this->server->os],
|
||||
// 'root_password' => $this->server->authentication['root_pass'],
|
||||
'ssh_keys' => [
|
||||
$sshKey->json()['ssh_key']['id'],
|
||||
],
|
||||
'name' => str($this->server->name)->slug(),
|
||||
'location' => $this->server->provider_data['region'],
|
||||
'server_type' => $this->server->provider_data['plan'],
|
||||
]);
|
||||
if ($create->status() != 201) {
|
||||
$this->providerError($create);
|
||||
}
|
||||
$this->server->jsonUpdate('provider_data', 'hetzner_id', $create->json()['server']['id'], false);
|
||||
$this->server->ip = $create->json()['server']['public_net']['ipv4']['ip'];
|
||||
$this->server->save();
|
||||
}
|
||||
|
||||
public function isRunning(): bool
|
||||
{
|
||||
$status = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->get($this->apiUrl.'/servers/'.$this->server->provider_data['hetzner_id']);
|
||||
|
||||
if (! $status->ok()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $status->json()['server']['status'] == 'running';
|
||||
}
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
if (isset($this->server->provider_data['hetzner_id'])) {
|
||||
$delete = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->delete($this->apiUrl.'/servers/'.$this->server->provider_data['hetzner_id']);
|
||||
|
||||
/** @todo notify */
|
||||
// if (! $delete->ok()) {
|
||||
// $this->server->team->notify(new FailedToDeleteServerFromProvider($this->server));
|
||||
// }
|
||||
}
|
||||
|
||||
// delete key
|
||||
if (isset($this->server->provider_data['ssh_key_id'])) {
|
||||
Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->delete($this->apiUrl.'/ssh_keys/'.$this->server->provider_data['ssh_key_id']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ServerProviderError
|
||||
*/
|
||||
private function providerError(Response $response): void
|
||||
{
|
||||
throw new ServerProviderError($response->json('error')['message']);
|
||||
}
|
||||
}
|
139
app/ServerProviders/Linode.php
Normal file
139
app/ServerProviders/Linode.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace App\ServerProviders;
|
||||
|
||||
use App\Exceptions\CouldNotConnectToProvider;
|
||||
use App\Exceptions\ServerProviderError;
|
||||
use App\Notifications\FailedToDeleteServerFromProvider;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class Linode extends AbstractProvider
|
||||
{
|
||||
protected string $apiUrl = 'https://api.linode.com/v4';
|
||||
|
||||
public function createValidationRules($input): array
|
||||
{
|
||||
$rules = [
|
||||
'os' => 'required|in:'.implode(',', config('core.operating_systems')),
|
||||
];
|
||||
// plans
|
||||
$plans = [];
|
||||
foreach (config('serverproviders.linode.plans') as $plan) {
|
||||
$plans[] = $plan['value'];
|
||||
}
|
||||
$rules['plan'] = 'required|in:'.implode(',', $plans);
|
||||
// regions
|
||||
$regions = [];
|
||||
foreach (config('serverproviders.linode.regions') as $region) {
|
||||
$regions[] = $region['value'];
|
||||
}
|
||||
$rules['region'] = 'required|in:'.implode(',', $regions);
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function credentialValidationRules($input): array
|
||||
{
|
||||
return [
|
||||
'token' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
public function credentialData($input): array
|
||||
{
|
||||
return [
|
||||
'token' => $input['token'],
|
||||
];
|
||||
}
|
||||
|
||||
public function data(array $input): array
|
||||
{
|
||||
return [
|
||||
'plan' => $input['plan'],
|
||||
'region' => $input['region'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CouldNotConnectToProvider
|
||||
*/
|
||||
public function connect(array $credentials = null): bool
|
||||
{
|
||||
$connect = Http::withToken($credentials['token'])->get($this->apiUrl.'/account');
|
||||
if (! $connect->ok()) {
|
||||
throw new CouldNotConnectToProvider('Linode');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function plans(): array
|
||||
{
|
||||
return config('serverproviders.linode.plans');
|
||||
}
|
||||
|
||||
public function regions(): array
|
||||
{
|
||||
return config('serverproviders.linode.regions');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ServerProviderError
|
||||
*/
|
||||
public function create(): void
|
||||
{
|
||||
$this->generateKeyPair();
|
||||
|
||||
$create = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->post($this->apiUrl.'/linode/instances', [
|
||||
'backups_enabled' => false,
|
||||
'image' => config('serverproviders.linode.images')[$this->server->os],
|
||||
'root_pass' => $this->server->authentication['root_pass'],
|
||||
'authorized_keys' => [
|
||||
$this->server->sshKey()['public_key'],
|
||||
],
|
||||
'booted' => true,
|
||||
'label' => str($this->server->name)->slug(),
|
||||
'type' => $this->server->provider_data['plan'],
|
||||
'region' => $this->server->provider_data['region'],
|
||||
]);
|
||||
if (! $create->ok()) {
|
||||
$msg = __('Failed to create server on Linode');
|
||||
$errors = $create->json('errors');
|
||||
if (count($errors) > 0) {
|
||||
$msg = $errors[0]['reason'];
|
||||
}
|
||||
throw new ServerProviderError($msg);
|
||||
}
|
||||
$this->server->ip = $create->json()['ipv4'][0];
|
||||
$providerData = $this->server->provider_data;
|
||||
$providerData['linode_id'] = $create->json()['id'];
|
||||
$this->server->provider_data = $providerData;
|
||||
$this->server->save();
|
||||
}
|
||||
|
||||
public function isRunning(): bool
|
||||
{
|
||||
$status = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->get($this->apiUrl.'/linode/instances/'.$this->server->provider_data['linode_id']);
|
||||
|
||||
if (! $status->ok()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $status->json()['status'] == 'running';
|
||||
}
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
if (isset($this->server->provider_data['linode_id'])) {
|
||||
$delete = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->delete($this->apiUrl.'/linode/instances/'.$this->server->provider_data['linode_id']);
|
||||
|
||||
/** @todo notify */
|
||||
// if (! $delete->ok()) {
|
||||
// $this->server->team->notify(new FailedToDeleteServerFromProvider($this->server));
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
152
app/ServerProviders/Vultr.php
Normal file
152
app/ServerProviders/Vultr.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\ServerProviders;
|
||||
|
||||
use App\Exceptions\CouldNotConnectToProvider;
|
||||
use App\Exceptions\ServerProviderError;
|
||||
use App\Notifications\FailedToDeleteServerFromProvider;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class Vultr extends AbstractProvider
|
||||
{
|
||||
protected string $apiUrl = 'https://api.vultr.com/v2';
|
||||
|
||||
public function createValidationRules($input): array
|
||||
{
|
||||
$rules = [
|
||||
'os' => 'required|in:'.implode(',', config('core.operating_systems')),
|
||||
];
|
||||
// plans
|
||||
$plans = [];
|
||||
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
|
||||
{
|
||||
return [
|
||||
'token' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
public function credentialData($input): array
|
||||
{
|
||||
return [
|
||||
'token' => $input['token'],
|
||||
];
|
||||
}
|
||||
|
||||
public function data(array $input): array
|
||||
{
|
||||
return [
|
||||
'plan' => $input['plan'],
|
||||
'region' => $input['region'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CouldNotConnectToProvider
|
||||
*/
|
||||
public function connect(array $credentials = null): bool
|
||||
{
|
||||
$connect = Http::withToken($credentials['token'])->get($this->apiUrl.'/account');
|
||||
if (! $connect->ok()) {
|
||||
throw new CouldNotConnectToProvider('Vultr');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function plans(): array
|
||||
{
|
||||
return config('serverproviders.vultr.plans');
|
||||
}
|
||||
|
||||
public function regions(): array
|
||||
{
|
||||
return config('serverproviders.vultr.regions');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ServerProviderError
|
||||
*/
|
||||
public function create(): void
|
||||
{
|
||||
// generate key pair
|
||||
generate_key_pair(Storage::disk(config('core.key_pairs_disk'))->path((string) $this->server->id));
|
||||
|
||||
$createSshKey = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->post($this->apiUrl.'/ssh-keys', [
|
||||
'ssh_key' => $this->server->sshKey()['public_key'],
|
||||
'name' => $this->server->name.'_'.$this->server->id,
|
||||
]);
|
||||
if ($createSshKey->status() != 201) {
|
||||
throw new ServerProviderError('Error creating SSH Key on Vultr');
|
||||
}
|
||||
|
||||
$create = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->post($this->apiUrl.'/instances', [
|
||||
'label' => $this->server->name,
|
||||
'region' => $this->server->provider_data['region'],
|
||||
'plan' => $this->server->provider_data['plan'],
|
||||
'os_id' => config('serverproviders.vultr.images')[$this->server->os],
|
||||
'enable_ipv6' => false,
|
||||
'sshkey_id' => [$createSshKey->json()['ssh_key']['id']],
|
||||
]);
|
||||
if ($create->status() != 202) {
|
||||
$msg = __('Failed to create server on Vultr');
|
||||
Log::error('Failed to create server on Vultr', $create->json());
|
||||
throw new ServerProviderError($msg);
|
||||
}
|
||||
$providerData = $this->server->provider_data;
|
||||
$providerData['instance_id'] = $create->json()['instance']['id'];
|
||||
$this->server->provider_data = $providerData;
|
||||
$this->server->save();
|
||||
}
|
||||
|
||||
public function isRunning(): bool
|
||||
{
|
||||
$status = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->get($this->apiUrl.'/instances/'.$this->server->provider_data['instance_id']);
|
||||
|
||||
if (! $status->ok()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->server->ip) {
|
||||
$this->server->ip = $status->json()['instance']['main_ip'];
|
||||
$this->server->save();
|
||||
}
|
||||
|
||||
return $status->json()['instance']['status'] == 'active';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
if (isset($this->server->provider_data['instance_id'])) {
|
||||
$delete = Http::withToken($this->server->serverProvider->credentials['token'])
|
||||
->delete($this->apiUrl.'/instances/'.$this->server->provider_data['instance_id']);
|
||||
|
||||
/** @todo notify */
|
||||
// if (! $delete->ok()) {
|
||||
// $this->server->team->notify(new FailedToDeleteServerFromProvider($this->server));
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user