diff --git a/app/Exceptions/Http/Connection/DaemonConnectionException.php b/app/Exceptions/Http/Connection/DaemonConnectionException.php index 1d9c01014..b9b8ac8fa 100644 --- a/app/Exceptions/Http/Connection/DaemonConnectionException.php +++ b/app/Exceptions/Http/Connection/DaemonConnectionException.php @@ -2,8 +2,8 @@ namespace App\Exceptions\Http\Connection; +use Exception; use Illuminate\Http\Response; -use GuzzleHttp\Exception\GuzzleException; use App\Exceptions\DisplayException; use Illuminate\Support\Facades\Context; @@ -22,7 +22,7 @@ class DaemonConnectionException extends DisplayException /** * Throw a displayable exception caused by a daemon connection error. */ - public function __construct(GuzzleException $previous, bool $useStatusCode = true) + public function __construct(?Exception $previous, bool $useStatusCode = true) { /** @var \GuzzleHttp\Psr7\Response|null $response */ $response = method_exists($previous, 'getResponse') ? $previous->getResponse() : null; diff --git a/app/Filament/Admin/Pages/Settings.php b/app/Filament/Admin/Pages/Settings.php index 783422264..9c3130642 100644 --- a/app/Filament/Admin/Pages/Settings.php +++ b/app/Filament/Admin/Pages/Settings.php @@ -26,8 +26,7 @@ use Filament\Notifications\Notification; use Filament\Pages\Concerns\InteractsWithHeaderActions; use Filament\Pages\Page; use Filament\Support\Enums\MaxWidth; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\GuzzleException; +use Illuminate\Http\Client\Factory; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Notification as MailNotification; use Illuminate\Support\HtmlString; @@ -164,22 +163,23 @@ class Settings extends Page implements HasForms ->label('Set to Cloudflare IPs') ->icon('tabler-brand-cloudflare') ->authorize(fn () => auth()->user()->can('update settings')) - ->action(function (Client $client, Set $set) { + ->action(function (Factory $client, Set $set) { $ips = collect(); + try { - $response = $client->request( - 'GET', - 'https://api.cloudflare.com/client/v4/ips', - config('panel.guzzle') - ); + $response = $client + ->timeout(3) + ->connectTimeout(3) + ->get('https://api.cloudflare.com/client/v4/ips'); + if ($response->getStatusCode() === 200) { - $result = json_decode($response->getBody(), true)['result']; + $result = $response->json('result'); foreach (['ipv4_cidrs', 'ipv6_cidrs'] as $value) { $ips->push(...data_get($result, $value)); } $ips->unique(); } - } catch (GuzzleException $e) { + } catch (Exception) { } $set('TRUSTED_PROXIES', $ips->values()->all()); @@ -245,12 +245,12 @@ class Settings extends Page implements HasForms ->columnSpanFull() ->inline() ->options([ - 'log' => 'Print mails to Log', + 'log' => '/storage/logs Directory', 'smtp' => 'SMTP Server', - 'sendmail' => 'sendmail Binary', 'mailgun' => 'Mailgun', 'mandrill' => 'Mandrill', 'postmark' => 'Postmark', + 'sendmail' => 'sendmail (PHP)', ]) ->live() ->default(env('MAIL_MAILER', config('mail.default'))) diff --git a/app/Filament/Admin/Resources/ServerResource/Pages/CreateServer.php b/app/Filament/Admin/Resources/ServerResource/Pages/CreateServer.php index 1931512cc..f56957edc 100644 --- a/app/Filament/Admin/Resources/ServerResource/Pages/CreateServer.php +++ b/app/Filament/Admin/Resources/ServerResource/Pages/CreateServer.php @@ -34,7 +34,9 @@ use Filament\Forms\Components\Wizard\Step; use Filament\Forms\Form; use Filament\Forms\Get; use Filament\Forms\Set; +use Filament\Notifications\Notification; use Filament\Resources\Pages\CreateRecord; +use Filament\Support\Exceptions\Halt; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Blade; @@ -873,7 +875,18 @@ class CreateServer extends CreateRecord { $data['allocation_additional'] = collect($data['allocation_additional'])->filter()->all(); - return $this->serverCreationService->handle($data); + try { + return $this->serverCreationService->handle($data); + } catch (Exception $exception) { + Notification::make() + ->title('Could not create server') + ->body($exception->getMessage()) + ->color('danger') + ->danger() + ->send(); + + throw new Halt(); + } } private function shouldHideComponent(Get $get, Component $component): bool diff --git a/app/Models/Server.php b/app/Models/Server.php index 548090d42..62dfe0945 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -5,12 +5,11 @@ namespace App\Models; use App\Enums\ContainerStatus; use App\Enums\ServerResourceType; use App\Enums\ServerState; -use App\Exceptions\Http\Connection\DaemonConnectionException; use App\Repositories\Daemon\DaemonServerRepository; use Carbon\CarbonInterface; -use GuzzleHttp\Exception\GuzzleException; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Http\Client\ConnectionException; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Arr; @@ -421,17 +420,13 @@ class Server extends Model /** * Sends a command or multiple commands to a running server instance. * - * @throws DaemonConnectionException|GuzzleException + * @throws ConnectionException */ public function send(array|string $command): ResponseInterface { - try { - return Http::daemon($this->node)->post("/api/servers/{$this->uuid}/commands", [ - 'commands' => is_array($command) ? $command : [$command], - ])->toPsrResponse(); - } catch (GuzzleException $exception) { - throw new DaemonConnectionException($exception); - } + return Http::daemon($this->node)->post("/api/servers/{$this->uuid}/commands", [ + 'commands' => is_array($command) ? $command : [$command], + ])->toPsrResponse(); } public function retrieveStatus(): string diff --git a/app/Repositories/Daemon/DaemonServerRepository.php b/app/Repositories/Daemon/DaemonServerRepository.php index a22c20f28..5be30e243 100644 --- a/app/Repositories/Daemon/DaemonServerRepository.php +++ b/app/Repositories/Daemon/DaemonServerRepository.php @@ -6,23 +6,16 @@ use App\Enums\ContainerStatus; use App\Enums\HttpStatusCode; use Exception; use Filament\Notifications\Notification; +use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\RequestException; -use Webmozart\Assert\Assert; -use App\Models\Server; -use GuzzleHttp\Exception\GuzzleException; -use App\Exceptions\Http\Connection\DaemonConnectionException; class DaemonServerRepository extends DaemonRepository { /** * Returns details about a server from the Daemon instance. - * - * @throws \App\Exceptions\Http\Connection\DaemonConnectionException */ public function getDetails(): array { - Assert::isInstanceOf($this->server, Server::class); - try { return $this->getHttpClient()->get( sprintf('/api/servers/%s', $this->server->uuid) @@ -53,131 +46,85 @@ class DaemonServerRepository extends DaemonRepository /** * Creates a new server on the daemon. * - * @throws \App\Exceptions\Http\Connection\DaemonConnectionException + * @throws ConnectionException */ public function create(bool $startOnCompletion = true): void { - Assert::isInstanceOf($this->server, Server::class); - - try { - $response = $this->getHttpClient()->post('/api/servers', [ - 'uuid' => $this->server->uuid, - 'start_on_completion' => $startOnCompletion, - ]); - } catch (GuzzleException $exception) { - throw new DaemonConnectionException($exception); - } + $this->getHttpClient()->post('/api/servers', [ + 'uuid' => $this->server->uuid, + 'start_on_completion' => $startOnCompletion, + ]); } /** * Triggers a server sync on daemon. * - * @throws \App\Exceptions\Http\Connection\DaemonConnectionException + * @throws ConnectionException */ public function sync(): void { - Assert::isInstanceOf($this->server, Server::class); - - try { - $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/sync"); - } catch (GuzzleException $exception) { - throw new DaemonConnectionException($exception); - } + $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/sync"); } /** * Delete a server from the daemon, forcibly if passed. * - * @throws \App\Exceptions\Http\Connection\DaemonConnectionException + * @throws ConnectionException */ public function delete(): void { - Assert::isInstanceOf($this->server, Server::class); - - try { - $this->getHttpClient()->delete('/api/servers/' . $this->server->uuid); - } catch (GuzzleException $exception) { - throw new DaemonConnectionException($exception); - } + $this->getHttpClient()->delete("/api/servers/{$this->server->uuid}"); } /** * Reinstall a server on the daemon. * - * @throws \App\Exceptions\Http\Connection\DaemonConnectionException + * @throws ConnectionException */ public function reinstall(): void { - Assert::isInstanceOf($this->server, Server::class); - - try { - $this->getHttpClient()->post(sprintf( - '/api/servers/%s/reinstall', - $this->server->uuid - )); - } catch (GuzzleException $exception) { - throw new DaemonConnectionException($exception); - } + $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/reinstall"); } /** * Requests the daemon to create a full archive of the server. Once the daemon is finished * they will send a POST request to "/api/remote/servers/{uuid}/archive" with a boolean. * - * @throws \App\Exceptions\Http\Connection\DaemonConnectionException + * @throws ConnectionException */ public function requestArchive(): void { - Assert::isInstanceOf($this->server, Server::class); - - try { - $this->getHttpClient()->post(sprintf( - '/api/servers/%s/archive', - $this->server->uuid - )); - } catch (GuzzleException $exception) { - throw new DaemonConnectionException($exception); - } + $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/archive"); } /** * Cancels a server transfer. * - * @throws \App\Exceptions\Http\Connection\DaemonConnectionException + * @throws ConnectionException */ public function cancelTransfer(): void { - Assert::isInstanceOf($this->server, Server::class); - - if ($transfer = $this->server->transfer) { - // Source node - $this->setNode($transfer->oldNode); - - try { - $this->getHttpClient()->delete(sprintf( - '/api/servers/%s/transfer', - $this->server->uuid - )); - } catch (GuzzleException $exception) { - throw new DaemonConnectionException($exception); - } - - // Destination node - $this->setNode($transfer->newNode); - - try { - $this->getHttpClient()->delete('/api/transfer', [ - 'json' => [ - 'server_id' => $this->server->uuid, - 'server' => [ - 'uuid' => $this->server->uuid, - ], - ], - ]); - } catch (GuzzleException $exception) { - throw new DaemonConnectionException($exception); - } + $transfer = $this->server->transfer; + if (!$transfer) { + return; } + + // Source node + $this->setNode($transfer->oldNode); + + $this->getHttpClient()->delete("/api/servers/{$this->server->uuid}/transfer"); + + // Destination node + $this->setNode($transfer->newNode); + + $this->getHttpClient()->delete('/api/transfer', [ + 'json' => [ + 'server_id' => $this->server->uuid, + 'server' => [ + 'uuid' => $this->server->uuid, + ], + ], + ]); } /** @@ -185,32 +132,13 @@ class DaemonServerRepository extends DaemonRepository * make it easier to revoke tokens on the fly. This ensures that the JTI key is formatted * correctly and avoids any costly mistakes in the codebase. * - * @throws \App\Exceptions\Http\Connection\DaemonConnectionException + * @throws ConnectionException */ public function revokeUserJTI(int $id): void { - Assert::isInstanceOf($this->server, Server::class); - - $this->revokeJTIs([md5($id . $this->server->uuid)]); - } - - /** - * Revokes an array of JWT JTI's by marking any token generated before the current time on - * the daemon instance as being invalid. - * - * @throws \App\Exceptions\Http\Connection\DaemonConnectionException - */ - protected function revokeJTIs(array $jtis): void - { - Assert::isInstanceOf($this->server, Server::class); - - try { - $this->getHttpClient() - ->post(sprintf('/api/servers/%s/ws/deny', $this->server->uuid), [ - 'jtis' => $jtis, - ]); - } catch (GuzzleException $exception) { - throw new DaemonConnectionException($exception); - } + $this->getHttpClient() + ->post("/api/servers/{$this->server->uuid}/ws/deny", [ + 'jtis' => [md5($id . $this->server->uuid)], + ]); } } diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index 78909d22d..bb802ceba 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -4,6 +4,7 @@ namespace App\Services\Servers; use App\Enums\ServerState; use App\Models\ServerVariable; +use Illuminate\Http\Client\ConnectionException; use Ramsey\Uuid\Uuid; use Illuminate\Support\Arr; use App\Models\User; @@ -16,7 +17,6 @@ use App\Models\Objects\DeploymentObject; use App\Repositories\Daemon\DaemonServerRepository; use App\Services\Deployment\FindViableNodesService; use App\Services\Deployment\AllocationSelectionService; -use App\Exceptions\Http\Connection\DaemonConnectionException; use App\Models\Egg; class ServerCreationService @@ -94,10 +94,10 @@ class ServerCreationService }, 5); try { - $this->daemonServerRepository->setServer($server)->create( - Arr::get($data, 'start_on_completion', false) ?? false - ); - } catch (DaemonConnectionException $exception) { + $this->daemonServerRepository + ->setServer($server) + ->create($data['start_on_completion'] ?? false); + } catch (ConnectionException $exception) { $this->serverDeletionService->withForce()->handle($server); throw $exception; diff --git a/tests/Integration/Services/Servers/ServerCreationServiceTest.php b/tests/Integration/Services/Servers/ServerCreationServiceTest.php index c0b82a285..94e982542 100644 --- a/tests/Integration/Services/Servers/ServerCreationServiceTest.php +++ b/tests/Integration/Services/Servers/ServerCreationServiceTest.php @@ -2,22 +2,19 @@ namespace App\Tests\Integration\Services\Servers; +use Illuminate\Http\Client\ConnectionException; use Mockery\MockInterface; use App\Models\Egg; -use GuzzleHttp\Psr7\Request; use App\Models\Node; use App\Models\User; -use GuzzleHttp\Psr7\Response; use App\Models\Server; use App\Models\Allocation; use Illuminate\Foundation\Testing\WithFaker; -use GuzzleHttp\Exception\BadResponseException; use Illuminate\Validation\ValidationException; use App\Models\Objects\DeploymentObject; use App\Tests\Integration\IntegrationTestCase; use App\Services\Servers\ServerCreationService; use App\Repositories\Daemon\DaemonServerRepository; -use App\Exceptions\Http\Connection\DaemonConnectionException; class ServerCreationServiceTest extends IntegrationTestCase { @@ -181,15 +178,11 @@ class ServerCreationServiceTest extends IntegrationTestCase ], ]; - $this->daemonServerRepository->expects('setServer->create')->andThrows( - new DaemonConnectionException( - new BadResponseException('Bad request', new Request('POST', '/create'), new Response(500)) - ) - ); + $this->daemonServerRepository->expects('setServer->create')->andThrows(new ConnectionException()); $this->daemonServerRepository->expects('setServer->delete')->andReturnUndefined(); - $this->expectException(DaemonConnectionException::class); + $this->expectException(ConnectionException::class); $this->getService()->handle($data);