Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions app/Actions/Database/StartDatabaseProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use App\Notifications\Container\ContainerRestarted;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;

Expand All @@ -29,11 +30,15 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
$proxyContainerName = "{$database->uuid}-proxy";
$isSSLEnabled = $database->enable_ssl ?? false;

if ($database->getMorphClass() === \App\Models\ServiceDatabase::class) {
if ($database->getMorphClass() === ServiceDatabase::class) {
$databaseType = $database->databaseType();
$network = $database->service->uuid;
$server = data_get($database, 'service.destination.server');
$containerName = "{$database->name}-{$database->service->uuid}";
$network = $database->parentNetworkName();
$server = $database->parentServer();
$containerName = $database->currentContainerName();
}

if (! $network || ! $server || ! $containerName) {
throw new \Exception('Unable to resolve database network/container/server for proxy startup.');
}
$internalPort = match ($databaseType) {
'standalone-mariadb', 'standalone-mysql' => 3306,
Expand Down Expand Up @@ -129,10 +134,11 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
$database->update(['is_public' => false]);

$team = data_get($database, 'environment.project.team')
?? data_get($database, 'service.environment.project.team');
?? data_get($database, 'service.environment.project.team')
?? $database->team();

$team?->notify(
new \App\Notifications\Container\ContainerRestarted(
new ContainerRestarted(
"TCP Proxy for {$database->name} database has been disabled due to error: {$e->getMessage()}",
$server,
)
Expand Down
4 changes: 2 additions & 2 deletions app/Actions/Database/StopDatabaseProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
{
$server = data_get($database, 'destination.server');
$uuid = $database->uuid;
if ($database->getMorphClass() === \App\Models\ServiceDatabase::class) {
$server = data_get($database, 'service.server');
if ($database->getMorphClass() === ServiceDatabase::class) {
$server = $database->parentServer();
}
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);

Expand Down
22 changes: 9 additions & 13 deletions app/Jobs/DatabaseBackupJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function handle(): void
}
if (data_get($this->backup, 'database_type') === ServiceDatabase::class) {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->service->server;
$this->server = $this->database->parentServer();
$this->s3 = $this->backup->s3;
} else {
$this->database = data_get($this->backup, 'database');
Expand All @@ -110,7 +110,8 @@ public function handle(): void
BackupCreated::dispatch($this->team->id);

$status = str(data_get($this->database, 'status'));
if (! $status->startsWith('running') && $this->database->id !== 0) {
$isApplicationBackedServiceDatabase = $this->database instanceof ServiceDatabase && filled($this->database->application_id);
if (! $isApplicationBackedServiceDatabase && ! $status->startsWith('running') && $this->database->id !== 0) {
Log::info('DatabaseBackupJob skipped: database not running', [
'backup_id' => $this->backup->id,
'database_id' => $this->database->id,
Expand All @@ -121,11 +122,12 @@ public function handle(): void
}
if (data_get($this->backup, 'database_type') === ServiceDatabase::class) {
$databaseType = $this->database->databaseType();
$serviceUuid = $this->database->service->uuid;
$serviceName = str($this->database->service->name)->slug();
$this->container_name = $this->database->currentContainerName();
$this->directory_name = $this->database->backupDirectoryName();
if (blank($this->container_name) || blank($this->directory_name)) {
throw new \Exception('Unable to resolve the running container for compose database backup.');
}
if (str($databaseType)->contains('postgres')) {
$this->container_name = "{$this->database->name}-$serviceUuid";
$this->directory_name = $serviceName.'-'.$this->container_name;
$commands[] = "docker exec $this->container_name env | grep POSTGRES_";
$envs = instant_remote_process($commands, $this->server, true, false, null, disableMultiplexing: true);
$envs = str($envs)->explode("\n");
Expand Down Expand Up @@ -155,8 +157,6 @@ public function handle(): void
$this->postgres_password = str($this->postgres_password)->after('POSTGRES_PASSWORD=')->value();
}
} elseif (str($databaseType)->contains('mysql')) {
$this->container_name = "{$this->database->name}-$serviceUuid";
$this->directory_name = $serviceName.'-'.$this->container_name;
$commands[] = "docker exec $this->container_name env | grep MYSQL_";
$envs = instant_remote_process($commands, $this->server, true, false, null, disableMultiplexing: true);
$envs = str($envs)->explode("\n");
Expand All @@ -178,8 +178,6 @@ public function handle(): void
throw new \Exception('MYSQL_DATABASE not found');
}
} elseif (str($databaseType)->contains('mariadb')) {
$this->container_name = "{$this->database->name}-$serviceUuid";
$this->directory_name = $serviceName.'-'.$this->container_name;
$commands[] = "docker exec $this->container_name env";
$envs = instant_remote_process($commands, $this->server, true, false, null, disableMultiplexing: true);
$envs = str($envs)->explode("\n");
Expand Down Expand Up @@ -216,8 +214,6 @@ public function handle(): void
}
} elseif (str($databaseType)->contains('mongo')) {
$databasesToBackup = ['*'];
$this->container_name = "{$this->database->name}-$serviceUuid";
$this->directory_name = $serviceName.'-'.$this->container_name;

// Try to extract MongoDB credentials from environment variables
try {
Expand Down Expand Up @@ -674,7 +670,7 @@ private function upload_to_s3(): void
$endpoint = $this->s3->endpoint;
$this->s3->testConnection(shouldSave: true);
if (data_get($this->backup, 'database_type') === ServiceDatabase::class) {
$network = $this->database->service->destination->network;
$network = $this->database->parentNetworkName();
} else {
$network = $this->database->destination->network;
}
Expand Down
61 changes: 61 additions & 0 deletions app/Livewire/Project/Application/ComposeDatabaseBackups.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace App\Livewire\Project\Application;

use App\Models\Application;
use App\Models\ServiceDatabase;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;

class ComposeDatabaseBackups extends Component
{
use AuthorizesRequests;

public ?Application $application = null;

public ?ServiceDatabase $serviceDatabase = null;

public array $parameters;

public bool $isImportSupported = false;

public function mount()
{
try {
$this->parameters = get_route_parameters();
$this->application = Application::whereUuid($this->parameters['application_uuid'])->first();
if (! $this->application) {
return redirect()->route('dashboard');
}
$this->authorize('view', $this->application);

$this->serviceDatabase = $this->application->databases()->whereUuid($this->parameters['stack_service_uuid'])->first();
if (! $this->serviceDatabase) {
return redirect()->route('project.application.configuration', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_uuid' => $this->parameters['environment_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
]);
}

if (! $this->serviceDatabase->isBackupSolutionAvailable() && ! $this->serviceDatabase->is_migrated) {
return redirect()->route('project.application.configuration', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_uuid' => $this->parameters['environment_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
]);
}

$dbType = $this->serviceDatabase->databaseType();
$supportedTypes = ['mysql', 'mariadb', 'postgres', 'mongo'];
$this->isImportSupported = collect($supportedTypes)->contains(fn ($type) => str_contains($dbType, $type));
} catch (\Throwable $e) {
return handleError($e, $this);
}
}

public function render()
{
return view('livewire.project.application.compose-database-backups');
}
}
20 changes: 15 additions & 5 deletions app/Livewire/Project/Database/BackupEdit.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Livewire\Project\Database;

use App\Models\ScheduledDatabaseBackup;
use App\Models\ServiceDatabase;
use Exception;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Locked;
Expand Down Expand Up @@ -144,8 +145,8 @@ public function delete($password, $selectedActions = [])

try {
$server = null;
if ($this->backup->database instanceof \App\Models\ServiceDatabase) {
$server = $this->backup->database->service->destination->server;
if ($this->backup->database instanceof ServiceDatabase) {
$server = $this->backup->database->parentServer();
} elseif ($this->backup->database->destination && $this->backup->database->destination->server) {
$server = $this->backup->database->destination->server;
}
Expand All @@ -170,9 +171,18 @@ public function delete($password, $selectedActions = [])

$this->backup->delete();

if ($this->backup->database->getMorphClass() === \App\Models\ServiceDatabase::class) {
if ($this->backup->database->getMorphClass() === ServiceDatabase::class) {
$serviceDatabase = $this->backup->database;

if ($serviceDatabase->application) {
return redirect()->route('project.application.compose-database.backups', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_uuid' => $this->parameters['environment_uuid'],
'application_uuid' => $serviceDatabase->application->uuid,
'stack_service_uuid' => $serviceDatabase->uuid,
]);
}

return redirect()->route('project.service.database.backups', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_uuid' => $this->parameters['environment_uuid'],
Expand All @@ -182,7 +192,7 @@ public function delete($password, $selectedActions = [])
} else {
return redirect()->route('project.database.backup.index', $this->parameters);
}
} catch (\Exception $e) {
} catch (Exception $e) {
$this->dispatch('error', 'Failed to delete backup: '.$e->getMessage());

return handleError($e, $this);
Expand Down Expand Up @@ -214,7 +224,7 @@ private function customValidate()

$isValid = validate_cron_expression($this->backup->frequency);
if (! $isValid) {
throw new \Exception('Invalid Cron / Human expression');
throw new Exception('Invalid Cron / Human expression');
}
$this->validate();
}
Expand Down
9 changes: 5 additions & 4 deletions app/Livewire/Project/Database/BackupExecutions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Livewire\Project\Database;

use App\Models\ScheduledDatabaseBackup;
use App\Models\ServiceDatabase;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
Expand Down Expand Up @@ -78,8 +79,8 @@ public function deleteBackup($executionId, $password, $selectedActions = [])
return;
}

$server = $execution->scheduledDatabaseBackup->database->getMorphClass() === \App\Models\ServiceDatabase::class
? $execution->scheduledDatabaseBackup->database->service->destination->server
$server = $execution->scheduledDatabaseBackup->database->getMorphClass() === ServiceDatabase::class
? $execution->scheduledDatabaseBackup->database->parentServer()
: $execution->scheduledDatabaseBackup->database->destination->server;

try {
Expand Down Expand Up @@ -185,8 +186,8 @@ public function server()
if ($this->database) {
$server = null;

if ($this->database instanceof \App\Models\ServiceDatabase) {
$server = $this->database->service->destination->server;
if ($this->database instanceof ServiceDatabase) {
$server = $this->database->parentServer();
} elseif ($this->database->destination && $this->database->destination->server) {
$server = $this->database->destination->server;
}
Expand Down
5 changes: 5 additions & 0 deletions app/Models/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,11 @@ public function fileStorages()
return $this->morphMany(LocalFileVolume::class, 'resource');
}

public function databases()
{
return $this->hasMany(ServiceDatabase::class);
}

public function type()
{
return 'application';
Expand Down
3 changes: 1 addition & 2 deletions app/Models/ScheduledDatabaseBackup.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ public function server()
{
if ($this->database) {
if ($this->database instanceof ServiceDatabase) {
$destination = data_get($this->database->service, 'destination');
$server = data_get($destination, 'server');
$server = $this->database->parentServer();
} else {
$destination = data_get($this->database, 'destination');
$server = data_get($destination, 'server');
Expand Down
Loading