mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(backups): use configured databases when container env is missing (#12212)
This commit is contained in:
@@ -217,7 +217,12 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($db) {
|
||||
$databasesToBackup = str($db)->after('MYSQL_DATABASE=')->value();
|
||||
} else {
|
||||
throw new \Exception('MYSQL_DATABASE not found');
|
||||
$databasesToBackup = $this->configuredDatabasesToBackup();
|
||||
if (blank($databasesToBackup)) {
|
||||
$this->recordUndeterminedDatabases('MYSQL_DATABASE');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
} elseif (str($databaseType)->contains('mariadb')) {
|
||||
$commands[] = "docker exec {$escapedContainerName} env";
|
||||
@@ -251,7 +256,12 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($db) {
|
||||
$databasesToBackup = str($db)->after('MYSQL_DATABASE=')->value();
|
||||
} else {
|
||||
throw new \Exception('MARIADB_DATABASE or MYSQL_DATABASE not found');
|
||||
$databasesToBackup = $this->configuredDatabasesToBackup();
|
||||
if (blank($databasesToBackup)) {
|
||||
$this->recordUndeterminedDatabases('MARIADB_DATABASE or MYSQL_DATABASE');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (str($databaseType)->contains('mongo')) {
|
||||
@@ -1017,6 +1027,38 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Databases picked on the backup, for service databases whose container does not name one.
|
||||
*
|
||||
* @return string|array<int, string>|null
|
||||
*/
|
||||
private function configuredDatabasesToBackup(): string|array|null
|
||||
{
|
||||
if ($this->backup->dump_all) {
|
||||
return ['all'];
|
||||
}
|
||||
|
||||
return $this->backup->databases_to_backup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a run that cannot start because no database is known in the execution list, and notify the team.
|
||||
*/
|
||||
private function recordUndeterminedDatabases(string $envNames): void
|
||||
{
|
||||
$message = "No database to back up: the container does not set {$envNames}. Select \"All databases\" or enter the databases to back up in the backup settings.";
|
||||
$this->recordSkippedBackup($message);
|
||||
$this->logSkippedRun('no_databases_determined');
|
||||
try {
|
||||
$this->team->notify(new BackupFailed($this->backup, $this->database, $message, 'unknown'));
|
||||
} catch (Throwable $e) {
|
||||
Log::channel('scheduled-errors')->warning('Failed to send backup failure notification', [
|
||||
'backup_id' => $this->backup->uuid,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function markStaleExecutionsAsFailed(): void
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -277,6 +277,58 @@ it('stores service database backups in the folder of earlier releases', function
|
||||
->toContain("/rallly-rallly_db-{$service->uuid}/pg-dump-rallly-");
|
||||
});
|
||||
|
||||
function backupRunSafetyServiceDatabase(Server $server, StandaloneDocker $destination, Environment $environment, string $image): ServiceDatabase
|
||||
{
|
||||
$service = Service::factory()->create([
|
||||
'server_id' => $server->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
'environment_id' => $environment->id,
|
||||
]);
|
||||
|
||||
return ServiceDatabase::create([
|
||||
'service_id' => $service->id,
|
||||
'name' => 'db',
|
||||
'image' => $image,
|
||||
'status' => 'running:healthy',
|
||||
]);
|
||||
}
|
||||
|
||||
it('backs up a service database without a database env variable from the backup settings', function (string $image, string $env, array $settings, string $databaseName) {
|
||||
$database = backupRunSafetyServiceDatabase($this->server, $this->destination, $this->environment, $image);
|
||||
$backup = backupRunSafetySchedule($database, $this->team, $settings);
|
||||
backupRunSafetyFakeRemoteCommands(["docker exec '{$database->name}-{$database->service->uuid}' env" => $env]);
|
||||
|
||||
(new DatabaseBackupJob($backup))->handle();
|
||||
|
||||
expect(ScheduledDatabaseBackupExecution::query()->sole())
|
||||
->status->toBe('success')
|
||||
->database_name->toBe($databaseName);
|
||||
})->with([
|
||||
'mariadb, all databases' => ['mariadb:11', 'MARIADB_ROOT_PASSWORD=secret', ['dump_all' => true], 'all'],
|
||||
'mariadb, selected database' => ['mariadb:11', 'MARIADB_ROOT_PASSWORD=secret', ['databases_to_backup' => 'shop'], 'shop'],
|
||||
'mysql, all databases' => ['mysql:8', 'MYSQL_ROOT_PASSWORD=secret', ['dump_all' => true], 'all'],
|
||||
'mysql, selected database' => ['mysql:8', 'MYSQL_ROOT_PASSWORD=secret', ['databases_to_backup' => 'shop'], 'shop'],
|
||||
]);
|
||||
|
||||
it('records a failed execution and notifies when no service database to back up is known', function (string $image, string $env) {
|
||||
$database = backupRunSafetyServiceDatabase($this->server, $this->destination, $this->environment, $image);
|
||||
$backup = backupRunSafetySchedule($database, $this->team);
|
||||
$this->team->emailNotificationSettings->update(['smtp_enabled' => true, 'backup_failure_email_notifications' => true]);
|
||||
$commands = backupRunSafetyFakeRemoteCommands(["docker exec '{$database->name}-{$database->service->uuid}' env" => $env]);
|
||||
|
||||
(new DatabaseBackupJob($backup))->handle();
|
||||
|
||||
expect(ScheduledDatabaseBackupExecution::query()->sole())
|
||||
->status->toBe('failed')
|
||||
->message->toContain('No database to back up');
|
||||
expect($commands->contains(fn (string $command) => str_contains($command, 'dump')))->toBeFalse();
|
||||
Notification::assertSentTo($this->team, BackupFailed::class);
|
||||
})->with([
|
||||
'mariadb' => ['mariadb:11', 'MARIADB_ROOT_PASSWORD=secret'],
|
||||
'mysql' => ['mysql:8', 'MYSQL_ROOT_PASSWORD=secret'],
|
||||
]);
|
||||
|
||||
// --- Worker timeout (M16) ---
|
||||
|
||||
it('stops the backup command at the backup timeout, before the worker timeout', function () {
|
||||
|
||||
Reference in New Issue
Block a user