mirror of
https://github.com/tiennm99/coolify.git
synced 2026-09-04 20:17:49 +00:00
fix(backups): validate S3 storage before backup scheduling
Prevent scheduled database backups from enabling S3 uploads without a valid team-owned storage configuration, and preserve the previous S3 storage ID in missing-storage error messages. Add coverage for backup edit/create validation and S3 upload failure messaging.
This commit is contained in:
@@ -668,12 +668,14 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
private function upload_to_s3(): void
|
||||
{
|
||||
if (is_null($this->s3)) {
|
||||
$previousS3StorageId = $this->backup->s3_storage_id;
|
||||
|
||||
$this->backup->update([
|
||||
'save_s3' => false,
|
||||
's3_storage_id' => null,
|
||||
]);
|
||||
|
||||
throw new \Exception('S3 storage configuration is missing or has been deleted (S3 storage ID: '.($this->backup->s3_storage_id ?? 'null').'). S3 backup has been disabled for this schedule.');
|
||||
throw new \Exception('S3 storage configuration is missing or has been deleted (S3 storage ID: '.($previousS3StorageId ?? 'null').'). S3 backup has been disabled for this schedule.');
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -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;
|
||||
@@ -144,7 +145,7 @@ class BackupEdit extends Component
|
||||
|
||||
try {
|
||||
$server = null;
|
||||
if ($this->backup->database instanceof \App\Models\ServiceDatabase) {
|
||||
if ($this->backup->database instanceof ServiceDatabase) {
|
||||
$server = $this->backup->database->service->destination->server;
|
||||
} elseif ($this->backup->database->destination && $this->backup->database->destination->server) {
|
||||
$server = $this->backup->database->destination->server;
|
||||
@@ -170,7 +171,7 @@ class BackupEdit extends Component
|
||||
|
||||
$this->backup->delete();
|
||||
|
||||
if ($this->backup->database->getMorphClass() === \App\Models\ServiceDatabase::class) {
|
||||
if ($this->backup->database->getMorphClass() === ServiceDatabase::class) {
|
||||
$serviceDatabase = $this->backup->database;
|
||||
|
||||
return redirect()->route('project.service.database.backups', [
|
||||
@@ -182,7 +183,7 @@ class BackupEdit extends Component
|
||||
} 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);
|
||||
@@ -207,6 +208,13 @@ class BackupEdit extends Component
|
||||
$this->backup->s3_storage_id = null;
|
||||
}
|
||||
|
||||
// S3 backup cannot be enabled without a valid S3 storage owned by the team
|
||||
$availableS3Ids = collect($this->s3s)->pluck('id');
|
||||
if ($this->backup->save_s3 && ! $availableS3Ids->contains($this->backup->s3_storage_id)) {
|
||||
$this->backup->save_s3 = $this->saveS3 = false;
|
||||
$this->backup->s3_storage_id = $this->s3StorageId = null;
|
||||
}
|
||||
|
||||
// Validate that disable_local_backup can only be true when S3 backup is enabled
|
||||
if ($this->backup->disable_local_backup && ! $this->backup->save_s3) {
|
||||
$this->backup->disable_local_backup = $this->disableLocalBackup = false;
|
||||
@@ -214,7 +222,7 @@ class BackupEdit extends Component
|
||||
|
||||
$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();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Livewire\Project\Database;
|
||||
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Attributes\Locked;
|
||||
@@ -48,6 +49,14 @@ class CreateScheduledBackup extends Component
|
||||
|
||||
$this->validate();
|
||||
|
||||
if ($this->saveToS3) {
|
||||
if (is_null($this->s3StorageId) || ! $this->definedS3s->contains('id', $this->s3StorageId)) {
|
||||
$this->dispatch('error', 'Please select a valid S3 storage to enable S3 backups.');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$isValid = validate_cron_expression($this->frequency);
|
||||
if (! $isValid) {
|
||||
$this->dispatch('error', 'Invalid Cron / Human expression.');
|
||||
@@ -74,7 +83,7 @@ class CreateScheduledBackup extends Component
|
||||
}
|
||||
|
||||
$databaseBackup = ScheduledDatabaseBackup::create($payload);
|
||||
if ($this->database->getMorphClass() === \App\Models\ServiceDatabase::class) {
|
||||
if ($this->database->getMorphClass() === ServiceDatabase::class) {
|
||||
$this->dispatch('refreshScheduledBackups', $databaseBackup->id);
|
||||
} else {
|
||||
$this->dispatch('refreshScheduledBackups');
|
||||
|
||||
@@ -15,6 +15,7 @@ class S3Storage extends BaseModel
|
||||
use HasFactory, HasSafeStringAttribute;
|
||||
|
||||
protected $fillable = [
|
||||
'team_id',
|
||||
'name',
|
||||
'description',
|
||||
'region',
|
||||
|
||||
@@ -23,6 +23,7 @@ class ScheduledDatabaseBackupExecution extends BaseModel
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'size' => 'integer',
|
||||
's3_uploaded' => 'boolean',
|
||||
'local_storage_deleted' => 'boolean',
|
||||
's3_storage_deleted' => 'boolean',
|
||||
|
||||
Reference in New Issue
Block a user