mirror of
https://github.com/coollabsio/coolify.git
synced 2026-09-26 09:20:54 -04:00
fix(storage): dispatch file saves after commit
This commit is contained in:
@@ -54,8 +54,7 @@ class LocalFileVolume extends BaseModel
|
||||
return;
|
||||
}
|
||||
|
||||
$fileVolume->load(['service']);
|
||||
dispatch(new ServerStorageSaveJob($fileVolume));
|
||||
ServerStorageSaveJob::dispatch($fileVolume)->afterCommit();
|
||||
});
|
||||
|
||||
static::deleting(function (LocalFileVolume $fileVolume): void {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Jobs\ServerStorageSaveJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\Environment;
|
||||
use App\Models\LocalFileVolume;
|
||||
@@ -169,3 +170,27 @@ it('defaults new service bind mounts to directories', function () {
|
||||
expect($fileVolume->content)->toBeNull()
|
||||
->and($fileVolume->is_directory)->toBeTrue();
|
||||
});
|
||||
|
||||
it('queues a new application mount once without loading its service relation', function () {
|
||||
$application = makeComposeApplication(DATA_DIR_COMPOSE);
|
||||
|
||||
applicationParser($application);
|
||||
applicationParser($application);
|
||||
|
||||
Bus::assertDispatchedTimes(ServerStorageSaveJob::class, 1);
|
||||
Bus::assertDispatched(ServerStorageSaveJob::class, function (ServerStorageSaveJob $job): bool {
|
||||
return ! $job->localFileVolume->relationLoaded('service') && $job->afterCommit === true;
|
||||
});
|
||||
});
|
||||
|
||||
it('queues a new service mount once without loading its service relation', function () {
|
||||
[$service] = makeComposeService(DATA_DIR_COMPOSE);
|
||||
|
||||
serviceParser($service);
|
||||
serviceParser($service);
|
||||
|
||||
Bus::assertDispatchedTimes(ServerStorageSaveJob::class, 1);
|
||||
Bus::assertDispatched(ServerStorageSaveJob::class, function (ServerStorageSaveJob $job): bool {
|
||||
return ! $job->localFileVolume->relationLoaded('service') && $job->afterCommit === true;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
use App\Jobs\ServerStorageSaveJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\Environment;
|
||||
use App\Models\LocalFileVolume;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Queue\Events\JobProcessed;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
|
||||
beforeEach(function () {
|
||||
Artisan::call('migrate:fresh', ['--force' => true, '--no-interaction' => true]);
|
||||
|
||||
config([
|
||||
'app.maintenance.store' => 'array',
|
||||
'cache.default' => 'array',
|
||||
'queue.default' => 'sync',
|
||||
'constants.ssh.mux_enabled' => false,
|
||||
]);
|
||||
|
||||
$team = Team::factory()->create();
|
||||
$key = PrivateKey::factory()->create(['team_id' => $team->id]);
|
||||
$server = Server::factory()->create(['team_id' => $team->id, 'private_key_id' => $key->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->application = Application::factory()->create([
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
|
||||
Process::fake(fn ($process) => Process::result(
|
||||
output: str_contains($process->command, 'realpath -m') ? 'OK' : 'NOK'
|
||||
));
|
||||
|
||||
$this->processedSaves = 0;
|
||||
Event::listen(JobProcessed::class, function (JobProcessed $event): void {
|
||||
if ($event->job->resolveName() === ServerStorageSaveJob::class) {
|
||||
$this->processedSaves++;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function createStorageSaveTestMount(Application $application): LocalFileVolume
|
||||
{
|
||||
return LocalFileVolume::create([
|
||||
'fs_path' => application_configuration_dir().'/'.$application->uuid.'/data',
|
||||
'mount_path' => '/app/data',
|
||||
'is_directory' => true,
|
||||
'resource_id' => $application->id,
|
||||
'resource_type' => $application->getMorphClass(),
|
||||
]);
|
||||
}
|
||||
|
||||
it('runs the storage save after its mount is committed', function () {
|
||||
DB::beginTransaction();
|
||||
$volume = createStorageSaveTestMount($this->application);
|
||||
|
||||
expect($this->processedSaves)->toBe(0);
|
||||
Process::assertNotRan(fn ($process) => str_contains($process->command, 'mkdir -p'));
|
||||
|
||||
DB::commit();
|
||||
|
||||
expect($this->processedSaves)->toBe(1)
|
||||
->and($volume->exists)->toBeTrue();
|
||||
Process::assertRan(fn ($process) => str_contains($process->command, 'mkdir -p'));
|
||||
});
|
||||
|
||||
it('does not run the storage save after its mount is rolled back', function () {
|
||||
DB::beginTransaction();
|
||||
createStorageSaveTestMount($this->application);
|
||||
DB::rollBack();
|
||||
|
||||
expect($this->processedSaves)->toBe(0)
|
||||
->and(LocalFileVolume::query()->count())->toBe(0);
|
||||
Process::assertNotRan(fn ($process) => str_contains($process->command, 'mkdir -p'));
|
||||
});
|
||||
|
||||
it('writes a new file mount immediately when there is no transaction', function () {
|
||||
LocalFileVolume::create([
|
||||
'fs_path' => application_configuration_dir().'/'.$this->application->uuid.'/config.env',
|
||||
'mount_path' => '/app/config.env',
|
||||
'content' => 'KEY=value',
|
||||
'is_directory' => false,
|
||||
'resource_id' => $this->application->id,
|
||||
'resource_type' => $this->application->getMorphClass(),
|
||||
]);
|
||||
|
||||
expect($this->processedSaves)->toBe(1);
|
||||
Process::assertRan(fn ($process) => str_contains($process->command, base64_encode('KEY=value')));
|
||||
});
|
||||
Reference in New Issue
Block a user