mirror of
https://github.com/coollabsio/coolify.git
synced 2026-10-29 10:04:36 +08:00
The compose parser queued ServerFilesFromServerJob for every volume on each parse, and pages that only show data parse the Compose file. Each sync also sent two SSH commands for each file. - Queue the sync only for a compose deployment, and for each service resource with file storages when the service starts or deploys. - Read the compose service names without a parse on the Domains and scheduled task pages. - Check all file storages with one remote command, and stop when a symbolic link, a special file, or an unknown state is at a file path. - Keep the application path in a file storage when a pull request preview is parsed. Preview deployments of the current parser write their `-pr-<id>` copies through LocalFileVolume::forPullRequest(). - Remove the unreachable file sync from the service resource page.
126 lines
5.4 KiB
PHP
126 lines
5.4 KiB
PHP
<?php
|
|
|
|
use App\Models\Environment;
|
|
use App\Models\InstanceSettings;
|
|
use App\Models\LocalFileVolume;
|
|
use App\Models\PrivateKey;
|
|
use App\Models\Project;
|
|
use App\Models\Server;
|
|
use App\Models\Service;
|
|
use App\Models\ServiceApplication;
|
|
use App\Models\StandaloneDocker;
|
|
use App\Models\Team;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Bus;
|
|
use Illuminate\Support\Facades\Process;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
Bus::fake();
|
|
InstanceSettings::forceCreate(['id' => 0]);
|
|
config([
|
|
'app.maintenance.store' => 'array',
|
|
'constants.ssh.mux_enabled' => false,
|
|
]);
|
|
|
|
$team = Team::factory()->create();
|
|
$server = Server::factory()->create([
|
|
'team_id' => $team->id,
|
|
'private_key_id' => PrivateKey::factory()->create(['team_id' => $team->id])->id,
|
|
]);
|
|
$destination = StandaloneDocker::query()->where('server_id', $server->id)->firstOrFail();
|
|
$project = Project::factory()->create(['team_id' => $team->id]);
|
|
$environment = Environment::factory()->create(['project_id' => $project->id]);
|
|
|
|
$this->service = Service::factory()->create([
|
|
'environment_id' => $environment->id,
|
|
'server_id' => $server->id,
|
|
'destination_id' => $destination->id,
|
|
'destination_type' => $destination->getMorphClass(),
|
|
'docker_compose_raw' => "services:\n app:\n image: nginx:alpine\n",
|
|
]);
|
|
$this->serviceApplication = ServiceApplication::create(['name' => 'app', 'service_id' => $this->service->id]);
|
|
|
|
Process::fake(fn ($process) => Process::result(output: match (true) {
|
|
str_contains(remoteProcessScript($process), 'empty-directory') => remoteFileStatesOutput('missing'),
|
|
str_contains(remoteProcessScript($process), 'readlink -f') => 'OK',
|
|
str_contains(remoteProcessScript($process), 'test -') => 'NOK',
|
|
default => '',
|
|
}));
|
|
});
|
|
|
|
function relativePathFileVolume(string $fsPath, bool $isDirectory, ?string $content = null): LocalFileVolume
|
|
{
|
|
return LocalFileVolume::create([
|
|
'fs_path' => $fsPath,
|
|
'mount_path' => '/data',
|
|
'content' => $content,
|
|
'is_directory' => $isDirectory,
|
|
'resource_id' => test()->serviceApplication->id,
|
|
'resource_type' => test()->serviceApplication->getMorphClass(),
|
|
])->fresh();
|
|
}
|
|
|
|
/**
|
|
* Remote commands run from the SSH user's home directory, so a relative path in a command
|
|
* would create files and directories in `~` instead of the resource directory.
|
|
*/
|
|
function assertNoRelativeRemotePath(): void
|
|
{
|
|
Process::assertDidntRun(fn ($process) => str_contains(remoteProcessScript($process), "'./"));
|
|
}
|
|
|
|
test('a relative directory path is created inside the resource directory, not in the home directory', function () {
|
|
$workdir = $this->service->workdir();
|
|
relativePathFileVolume('./data/sub', isDirectory: true)->saveStorageOnServer();
|
|
|
|
Process::assertRan(fn ($process) => str_contains(remoteProcessScript($process), "mkdir -p '{$workdir}/data/sub'"));
|
|
Process::assertRan(fn ($process) => str_contains(remoteProcessScript($process), "mkdir -p '{$workdir}/data'"));
|
|
assertNoRelativeRemotePath();
|
|
});
|
|
|
|
test('a relative file path without content gets its parent directory inside the resource directory', function () {
|
|
$workdir = $this->service->workdir();
|
|
relativePathFileVolume('./config/app.conf', isDirectory: false)->saveStorageOnServer();
|
|
|
|
Process::assertRan(fn ($process) => str_contains(remoteProcessScript($process), "mkdir -p '{$workdir}/config'"));
|
|
Process::assertRan(fn ($process) => str_contains(remoteProcessScript($process), "touch '{$workdir}/config/app.conf'"));
|
|
assertNoRelativeRemotePath();
|
|
});
|
|
|
|
test('a relative file path with content is written inside the resource directory', function () {
|
|
$workdir = $this->service->workdir();
|
|
relativePathFileVolume('./config/app.conf', isDirectory: false, content: 'listen 8080;')->saveStorageOnServer();
|
|
|
|
Process::assertRan(fn ($process) => str_contains(remoteProcessScript($process), "| base64 -d | tee '{$workdir}/config/app.conf' > /dev/null"));
|
|
assertNoRelativeRemotePath();
|
|
});
|
|
|
|
test('an absolute directory path keeps working unchanged', function () {
|
|
$workdir = $this->service->workdir();
|
|
relativePathFileVolume($workdir.'/data', isDirectory: true)->saveStorageOnServer();
|
|
|
|
Process::assertRan(fn ($process) => str_contains(remoteProcessScript($process), "mkdir -p '{$workdir}/data'"));
|
|
assertNoRelativeRemotePath();
|
|
});
|
|
|
|
test('deleting a relative path removes it from the resource directory', function () {
|
|
Process::fake(fn ($process) => Process::result(output: match (true) {
|
|
str_contains(remoteProcessScript($process), 'empty-directory') => remoteFileStatesOutput('file'),
|
|
str_contains(remoteProcessScript($process), 'readlink -f') => 'OK',
|
|
str_contains(remoteProcessScript($process), 'test -f') => 'OK',
|
|
default => 'NOK',
|
|
}));
|
|
$workdir = $this->service->workdir();
|
|
relativePathFileVolume('./config/app.conf', isDirectory: false)->deleteStorageOnServer();
|
|
|
|
Process::assertRan(fn ($process) => str_contains(remoteProcessScript($process), "rm -rf '{$workdir}/config/app.conf'"));
|
|
assertNoRelativeRemotePath();
|
|
});
|
|
|
|
test('the content path on the server resolves a relative path against the resource directory', function () {
|
|
expect(relativePathFileVolume('./config/app.conf', isDirectory: false, content: 'x')->contentPathOnServer())
|
|
->toBe($this->service->workdir().'/config/app.conf');
|
|
});
|