mirror of
https://github.com/coollabsio/coolify.git
synced 2026-08-28 10:05:25 -05:00
164 lines
6.4 KiB
PHP
164 lines
6.4 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Project\Database\ImportForm;
|
|
|
|
function importFormWithResource(string $modelClass): ImportForm
|
|
{
|
|
$component = new class extends ImportForm
|
|
{
|
|
public $resource;
|
|
};
|
|
|
|
$database = Mockery::mock($modelClass);
|
|
$database->shouldReceive('getMorphClass')->andReturn($modelClass);
|
|
$component->resource = $database;
|
|
|
|
return $component;
|
|
}
|
|
|
|
/**
|
|
* @return list<string>
|
|
*/
|
|
function invokedPostgresRestoreClients(string $contents, bool $gzip = false): array
|
|
{
|
|
$binDir = sys_get_temp_dir().'/coolify-pg-restore-'.bin2hex(random_bytes(8));
|
|
mkdir($binDir);
|
|
|
|
try {
|
|
$logFile = $binDir.'/invoked.log';
|
|
file_put_contents($logFile, '');
|
|
|
|
foreach (['pg_restore', 'psql'] as $tool) {
|
|
$stub = <<<SH
|
|
#!/bin/sh
|
|
printf '%s\\n' '{$tool}' >> '{$logFile}'
|
|
if [ ! -t 0 ]; then
|
|
cat >/dev/null
|
|
fi
|
|
exit 0
|
|
SH;
|
|
$path = $binDir.'/'.$tool;
|
|
file_put_contents($path, $stub);
|
|
chmod($path, 0755);
|
|
}
|
|
|
|
$dumpPath = $binDir.'/backup.dump';
|
|
$payload = $gzip ? gzencode($contents) : $contents;
|
|
expect($payload)->not->toBeFalse();
|
|
file_put_contents($dumpPath, $payload);
|
|
|
|
$component = importFormWithResource('App\Models\StandalonePostgresql');
|
|
$component->dumpAll = true;
|
|
$component->postgresqlRestoreCommand = ':';
|
|
|
|
$process = proc_open(
|
|
['sh', '-c', $component->buildRestoreCommand($dumpPath)],
|
|
[
|
|
0 => ['pipe', 'r'],
|
|
1 => ['pipe', 'w'],
|
|
2 => ['pipe', 'w'],
|
|
],
|
|
$pipes,
|
|
$binDir,
|
|
[
|
|
'PATH' => $binDir.PATH_SEPARATOR.(getenv('PATH') ?: '/usr/bin:/bin'),
|
|
'POSTGRES_USER' => 'postgres',
|
|
'POSTGRES_DB' => 'app',
|
|
]
|
|
);
|
|
|
|
expect($process)->not->toBeFalse();
|
|
|
|
fclose($pipes[0]);
|
|
stream_get_contents($pipes[1]);
|
|
stream_get_contents($pipes[2]);
|
|
fclose($pipes[1]);
|
|
fclose($pipes[2]);
|
|
|
|
expect(proc_close($process))->toBe(0);
|
|
|
|
return array_values(array_filter(explode("\n", trim((string) file_get_contents($logFile)))));
|
|
} finally {
|
|
foreach (glob($binDir.'/*') ?: [] as $file) {
|
|
unlink($file);
|
|
}
|
|
rmdir($binDir);
|
|
}
|
|
}
|
|
|
|
test('buildRestoreCommand handles PostgreSQL without dumpAll', function () {
|
|
$component = importFormWithResource('App\Models\StandalonePostgresql');
|
|
$component->dumpAll = false;
|
|
$component->postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d $POSTGRES_DB';
|
|
|
|
$result = $component->buildRestoreCommand('/tmp/test.dump');
|
|
|
|
expect($result)->toContain('pg_restore');
|
|
expect($result)->toContain('/tmp/test.dump');
|
|
});
|
|
|
|
test('buildRestoreCommand handles PostgreSQL with dumpAll', function () {
|
|
$component = importFormWithResource('App\Models\StandalonePostgresql');
|
|
$component->dumpAll = true;
|
|
$component->postgresqlRestoreCommand = 'psql -U $POSTGRES_USER -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname IS NOT NULL AND pid <> pg_backend_pid()" && psql -U $POSTGRES_USER -t -c "SELECT datname FROM pg_database WHERE NOT datistemplate" | xargs -I {} dropdb -U $POSTGRES_USER --if-exists {} && createdb -U $POSTGRES_USER postgres';
|
|
|
|
$result = $component->buildRestoreCommand('/tmp/test.dump');
|
|
|
|
expect($result)->toContain("gunzip -cf '/tmp/test.dump'");
|
|
expect($result)->toContain('psql -U ${POSTGRES_USER} -d ${POSTGRES_DB:-${POSTGRES_USER:-postgres}}');
|
|
});
|
|
|
|
test('buildRestoreCommand dump-all PostgreSQL restore is pg_restore for PGDMP otherwise psql', function () {
|
|
$component = importFormWithResource('App\Models\StandalonePostgresql');
|
|
$component->dumpAll = true;
|
|
$component->postgresqlRestoreCommand = 'psql -U ${POSTGRES_USER} -c "cleanup"';
|
|
|
|
$escapedTmpPath = escapeshellarg('/tmp/test.dump');
|
|
|
|
expect($component->buildRestoreCommand('/tmp/test.dump'))->toBe(
|
|
'psql -U ${POSTGRES_USER} -c "cleanup" && if [ "$({ gunzip -cf '.$escapedTmpPath.' 2>/dev/null || cat '.$escapedTmpPath.'; } | head -c 5)" = \'PGDMP\' ]; then pg_restore -U ${POSTGRES_USER} -d ${POSTGRES_DB:-${POSTGRES_USER:-postgres}} '.$escapedTmpPath.'; else (gunzip -cf '.$escapedTmpPath.' 2>/dev/null || cat '.$escapedTmpPath.') | psql -U ${POSTGRES_USER} -d ${POSTGRES_DB:-${POSTGRES_USER:-postgres}}; fi'
|
|
);
|
|
});
|
|
|
|
test('dump-all PostgreSQL restore selects the client for the dump format', function (string $contents, bool $gzip, string $client) {
|
|
expect(invokedPostgresRestoreClients($contents, $gzip))->toBe([$client]);
|
|
})->with([
|
|
'custom archive' => ['PGDMP'.str_repeat("\0", 16), false, 'pg_restore'],
|
|
'gzip custom archive' => ['PGDMP'.str_repeat("\0", 16), true, 'pg_restore'],
|
|
'plain SQL' => ["-- PostgreSQL database dump\nSELECT 1;\n", false, 'psql'],
|
|
'gzip SQL' => ["-- PostgreSQL database dump\nSELECT 1;\n", true, 'psql'],
|
|
]);
|
|
|
|
test('buildRestoreCommand handles MySQL without dumpAll', function () {
|
|
$component = importFormWithResource('App\Models\StandaloneMysql');
|
|
$component->dumpAll = false;
|
|
$component->mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE';
|
|
|
|
$result = $component->buildRestoreCommand('/tmp/test.dump');
|
|
|
|
expect($result)->toContain('mysql -u $MYSQL_USER');
|
|
expect($result)->toContain("< '/tmp/test.dump'");
|
|
});
|
|
|
|
test('buildRestoreCommand handles MariaDB without dumpAll', function () {
|
|
$component = importFormWithResource('App\Models\StandaloneMariadb');
|
|
$component->dumpAll = false;
|
|
$component->mariadbRestoreCommand = 'mariadb -u $MARIADB_USER -p$MARIADB_PASSWORD $MARIADB_DATABASE';
|
|
|
|
$result = $component->buildRestoreCommand('/tmp/test.dump');
|
|
|
|
expect($result)->toContain('mariadb -u $MARIADB_USER');
|
|
expect($result)->toContain("< '/tmp/test.dump'");
|
|
});
|
|
|
|
test('buildRestoreCommand always appends the MongoDB archive path', function (bool $dumpAll) {
|
|
$component = importFormWithResource('App\Models\StandaloneMongodb');
|
|
$component->dumpAll = $dumpAll;
|
|
$component->mongodbRestoreCommand = 'mongorestore --authenticationDatabase=admin --username $MONGO_INITDB_ROOT_USERNAME --password $MONGO_INITDB_ROOT_PASSWORD --uri mongodb://localhost:27017 --gzip --archive=';
|
|
|
|
$result = $component->buildRestoreCommand('/tmp/test.dump');
|
|
|
|
expect($result)->toContain('mongorestore');
|
|
expect($result)->toContain("--archive='/tmp/test.dump'");
|
|
})->with([false, true]);
|