mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
v4.7 (#12478)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Co-authored-by: Aditya Tripathi <aditya@climactic.co>
This commit is contained in:
co-authored by
Claude Opus 5.5
peaklabs-dev
Aditya Tripathi
parent
b7dc2e435b
commit
033f0c84da
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use App\Services\RcloneTransfer;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Process\PendingProcess;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
function rcloneTransferTestServer(string $user = 'root'): Server
|
||||
{
|
||||
$owner = User::factory()->create();
|
||||
$team = $owner->teams()->first();
|
||||
Storage::fake('ssh-keys');
|
||||
|
||||
return Server::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
'user' => $user,
|
||||
'private_key_id' => PrivateKey::factory()->create(['team_id' => $team->id])->id,
|
||||
]);
|
||||
}
|
||||
|
||||
function rcloneTransferTestStorage(): S3Storage
|
||||
{
|
||||
$storage = new S3Storage;
|
||||
$storage->forceFill(['type' => 'sftp', 'endpoint' => 'sftp://1.1.1.1/backups', 'key' => 'backup', 'secret' => 'password', 'sftp_host_key' => 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILhw/KsYTF9TQOwQX1EAM3gI5oxDS9Daz5nNuCixSRXT']);
|
||||
|
||||
return $storage;
|
||||
}
|
||||
|
||||
it('deletes the partial files of a failed upload, and only of that file', function (string $user, string $docker) {
|
||||
Process::fake(fn (PendingProcess $process) => str_contains(remoteProcessScript($process), "'copyto'")
|
||||
? Process::result(exitCode: 1, errorOutput: 'connection lost')
|
||||
: Process::result());
|
||||
$transfer = new RcloneTransfer(rcloneTransferTestStorage(), rcloneTransferTestServer($user), 'backup-of-x');
|
||||
|
||||
expect(fn () => $transfer->upload('/data/coolify/backups/db/pg-[1].dmp', '/data/coolify/backups/db/pg-[1].dmp'))
|
||||
->toThrow(RuntimeException::class, 'connection lost');
|
||||
|
||||
Process::assertRan(fn (PendingProcess $process): bool => str_contains(remoteProcessScript($process), "{$docker} exec 'backup-of-x' pkill -x rclone >/dev/null 2>&1 || ")
|
||||
&& str_contains(remoteProcessScript($process), "{$docker} exec 'backup-of-x' rclone --config /tmp/rclone.conf --contimeout 15s 'delete' 'storage:/backups/data/coolify/backups/db' '--max-depth' '1' '--include' '/pg-\\[1\\].dmp.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].partial'"));
|
||||
})->with([
|
||||
'root' => ['root', 'docker'],
|
||||
'non-root' => ['ubuntu', 'sudo docker'],
|
||||
]);
|
||||
|
||||
it('does not clean up after a successful upload', function () {
|
||||
Process::fake();
|
||||
$transfer = new RcloneTransfer(rcloneTransferTestStorage(), rcloneTransferTestServer(), 'backup-of-x');
|
||||
|
||||
$transfer->upload('/data/coolify/backups/db/pg.dmp', '/data/coolify/backups/db/pg.dmp');
|
||||
|
||||
Process::assertDidntRun(fn (PendingProcess $process): bool => str_contains(remoteProcessScript($process), '.partial'));
|
||||
});
|
||||
|
||||
/**
|
||||
* Loads config/constants.php with the given environment variables (null unsets one).
|
||||
*
|
||||
* @param array<string, ?string> $variables
|
||||
*/
|
||||
function rcloneImageFromEnv(array $variables): string
|
||||
{
|
||||
$original = [];
|
||||
foreach ($variables as $name => $value) {
|
||||
$original[$name] = [$_SERVER[$name] ?? null, $_ENV[$name] ?? null, getenv($name)];
|
||||
unset($_SERVER[$name], $_ENV[$name]);
|
||||
putenv($name);
|
||||
if ($value !== null) {
|
||||
$_SERVER[$name] = $_ENV[$name] = $value;
|
||||
putenv("{$name}={$value}");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return (require config_path('constants.php'))['coolify']['rclone_image'];
|
||||
} finally {
|
||||
foreach ($original as $name => [$server, $env, $putenv]) {
|
||||
unset($_SERVER[$name], $_ENV[$name]);
|
||||
if ($server !== null) {
|
||||
$_SERVER[$name] = $server;
|
||||
}
|
||||
if ($env !== null) {
|
||||
$_ENV[$name] = $env;
|
||||
}
|
||||
putenv($putenv === false ? $name : "{$name}={$putenv}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it('pulls the rclone image from the configured registry', function (?string $registry, string $expected) {
|
||||
expect(rcloneImageFromEnv(['REGISTRY_URL' => $registry, 'RCLONE_IMAGE' => null]))
|
||||
->toBe($expected.'/rclone/rclone:1.75.2@sha256:2687085f718d3c628f7fdfb77c52a1d344332aed543110bb88088f2c70d43eb5');
|
||||
})->with([
|
||||
'default' => [null, 'docker.io'],
|
||||
'docker hub' => ['docker.io', 'docker.io'],
|
||||
'github' => ['ghcr.io', 'ghcr.io'],
|
||||
]);
|
||||
|
||||
it('uses RCLONE_IMAGE as given', function () {
|
||||
expect(rcloneImageFromEnv(['REGISTRY_URL' => 'ghcr.io', 'RCLONE_IMAGE' => 'mirror.example.com/rclone/rclone:1.75.2']))
|
||||
->toBe('mirror.example.com/rclone/rclone:1.75.2');
|
||||
});
|
||||
|
||||
it('labels the rclone container and stops it after the timeout of its job', function (?int $timeout, int $lifetime) {
|
||||
Process::fake();
|
||||
config(['constants.coolify.rclone_image' => 'docker.io/rclone/rclone:1.75.2']);
|
||||
$transfer = new RcloneTransfer(rcloneTransferTestStorage(), rcloneTransferTestServer(), 'backup-of-x');
|
||||
|
||||
$transfer->start(timeout: $timeout);
|
||||
|
||||
Process::assertRan(fn (PendingProcess $process): bool => str_contains(remoteProcessScript($process),
|
||||
"docker run -d --rm --name 'backup-of-x' --label coolify.type=rclone --entrypoint sleep 'docker.io/rclone/rclone:1.75.2' {$lifetime} >/dev/null"));
|
||||
})->with([
|
||||
'job timeout' => [3600, 3900],
|
||||
'no timeout' => [null, 86400],
|
||||
]);
|
||||
|
||||
it('follows the registry setting of the instance for the default rclone image', function (string $configured, string $expected) {
|
||||
Process::fake();
|
||||
InstanceSettings::unguarded(fn () => InstanceSettings::updateOrCreate(['id' => 0], ['docker_registry_url' => 'ghcr.io']));
|
||||
config(['constants.coolify.registry_url' => 'docker.io', 'constants.coolify.rclone_image' => $configured]);
|
||||
$transfer = new RcloneTransfer(rcloneTransferTestStorage(), rcloneTransferTestServer(), 'backup-of-x');
|
||||
|
||||
$transfer->start();
|
||||
|
||||
Process::assertRan(fn (PendingProcess $process): bool => str_contains(remoteProcessScript($process), "--entrypoint sleep '{$expected}' 86400"));
|
||||
})->with([
|
||||
'default image' => ['docker.io/rclone/rclone:1.75.2@sha256:abc', 'ghcr.io/rclone/rclone:1.75.2@sha256:abc'],
|
||||
'custom image' => ['mirror.example.com/rclone/rclone:1.75.2', 'mirror.example.com/rclone/rclone:1.75.2'],
|
||||
]);
|
||||
Reference in New Issue
Block a user