fix(parser): preserve file volume state (#10843)

This commit is contained in:
Andras Bacsai 2026-07-03 10:14:39 +02:00 committed by GitHub
parent 08e0d02643
commit 67693acce7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 179 additions and 23 deletions

View file

@ -370,8 +370,6 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
$pullRequestId = $pull_request_id;
$isPullRequest = $pullRequestId == 0 ? false : true;
$server = data_get($resource, 'destination.server');
$fileStorages = $resource->fileStorages();
try {
$yaml = Yaml::parse($compose);
} catch (Exception) {
@ -703,14 +701,11 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
$source = $parsed['source'];
$target = $parsed['target'];
// Mode is available in $parsed['mode'] if needed
$foundConfig = $fileStorages->whereMountPath($target)->first();
$foundConfig = $originalResource->fileStorages()->whereMountPath($target)->first();
if (sourceIsLocal($source)) {
$type = str('bind');
if ($foundConfig) {
$contentNotNull_temp = data_get($foundConfig, 'content');
if ($contentNotNull_temp) {
$content = $contentNotNull_temp;
}
$content = data_get($foundConfig, 'content');
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
// By default, we cannot determine if the bind is a directory or not, so we set it to directory
@ -756,12 +751,9 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
}
}
$foundConfig = $fileStorages->whereMountPath($target)->first();
$foundConfig = $originalResource->fileStorages()->whereMountPath($target)->first();
if ($foundConfig) {
$contentNotNull_temp = data_get($foundConfig, 'content');
if ($contentNotNull_temp) {
$content = $contentNotNull_temp;
}
$content = data_get($foundConfig, 'content');
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
// if isDirectory is not set (or false) & content is also not set, we assume it is a directory
@ -2070,7 +2062,6 @@ function serviceParser(Service $resource): Collection
'service_id' => $resource->id,
]);
}
$fileStorages = $savedService->fileStorages();
if ($savedService->image !== $image) {
$savedService->image = $image;
$savedService->save();
@ -2090,14 +2081,11 @@ function serviceParser(Service $resource): Collection
$source = $parsed['source'];
$target = $parsed['target'];
// Mode is available in $parsed['mode'] if needed
$foundConfig = $fileStorages->whereMountPath($target)->first();
$foundConfig = $originalResource->fileStorages()->whereMountPath($target)->first();
if (sourceIsLocal($source)) {
$type = str('bind');
if ($foundConfig) {
$contentNotNull_temp = data_get($foundConfig, 'content');
if ($contentNotNull_temp) {
$content = $contentNotNull_temp;
}
$content = data_get($foundConfig, 'content');
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
// By default, we cannot determine if the bind is a directory or not, so we set it to directory
@ -2143,12 +2131,9 @@ function serviceParser(Service $resource): Collection
}
}
$foundConfig = $fileStorages->whereMountPath($target)->first();
$foundConfig = $originalResource->fileStorages()->whereMountPath($target)->first();
if ($foundConfig) {
$contentNotNull_temp = data_get($foundConfig, 'content');
if ($contentNotNull_temp) {
$content = $contentNotNull_temp;
}
$content = data_get($foundConfig, 'content');
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
// if isDirectory is not set (or false) & content is also not set, we assume it is a directory

View file

@ -0,0 +1,171 @@
<?php
use App\Models\Application;
use App\Models\Environment;
use App\Models\LocalFileVolume;
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;
uses(RefreshDatabase::class);
const TWO_FILE_COMPOSE = <<<'YAML'
services:
app:
image: nginx:latest
volumes:
- ./wg_config.conf:/app/ps/wg0.conf
- ./override_trays.json:/app/ps/override_tray.json
YAML;
const DATA_DIR_COMPOSE = <<<'YAML'
services:
app:
image: nginx:latest
volumes:
- ./data:/app/data
YAML;
beforeEach(function () {
Bus::fake();
$team = Team::factory()->create();
$server = Server::factory()->create(['team_id' => $team->id]);
$destination = StandaloneDocker::where('server_id', $server->id)->first();
$project = Project::factory()->create(['team_id' => $team->id]);
$environment = Environment::factory()->create(['project_id' => $project->id]);
$this->destination = $destination;
$this->environment = $environment;
});
function makeComposeApplication(string $dockerComposeRaw): Application
{
return Application::factory()->create([
'environment_id' => test()->environment->id,
'destination_id' => test()->destination->id,
'destination_type' => test()->destination->getMorphClass(),
'build_pack' => 'dockercompose',
'docker_compose_raw' => $dockerComposeRaw,
]);
}
/**
* @return array{0: Service, 1: ServiceApplication}
*/
function makeComposeService(string $dockerComposeRaw): array
{
$service = Service::factory()->create([
'environment_id' => test()->environment->id,
'server_id' => test()->destination->server_id,
'destination_id' => test()->destination->id,
'destination_type' => test()->destination->getMorphClass(),
'docker_compose_raw' => $dockerComposeRaw,
]);
$serviceApplication = ServiceApplication::create([
'name' => 'app',
'service_id' => $service->id,
]);
return [$service, $serviceApplication];
}
function seedFileVolume($resource, string $baseDir, string $fileName, string $mountPath, string $content): void
{
LocalFileVolume::create([
'fs_path' => "{$baseDir}/{$fileName}",
'mount_path' => $mountPath,
'content' => $content,
'is_directory' => false,
'resource_id' => $resource->id,
'resource_type' => $resource->getMorphClass(),
]);
}
it('preserves existing application file volume content when reparsing compose bind mounts', function () {
$application = makeComposeApplication(TWO_FILE_COMPOSE);
$baseDir = application_configuration_dir()."/{$application->uuid}";
seedFileVolume($application, $baseDir, 'wg_config.conf', '/app/ps/wg0.conf', 'test-conf');
seedFileVolume($application, $baseDir, 'override_trays.json', '/app/ps/override_tray.json', '0');
applicationParser($application);
$fileVolume = $application->fileStorages()->where('mount_path', '/app/ps/override_tray.json')->first();
expect($fileVolume->content)->toBe('0')
->and($fileVolume->is_directory)->toBeFalse();
});
it('keeps existing application file volumes as files when content is empty', function () {
$application = makeComposeApplication(TWO_FILE_COMPOSE);
$baseDir = application_configuration_dir()."/{$application->uuid}";
seedFileVolume($application, $baseDir, 'wg_config.conf', '/app/ps/wg0.conf', 'test-conf');
seedFileVolume($application, $baseDir, 'override_trays.json', '/app/ps/override_tray.json', '');
applicationParser($application);
$fileVolume = $application->fileStorages()->where('mount_path', '/app/ps/override_tray.json')->first();
expect($fileVolume->content)->toBe('')
->and($fileVolume->is_directory)->toBeFalse();
});
it('defaults new application bind mounts to directories', function () {
$application = makeComposeApplication(DATA_DIR_COMPOSE);
applicationParser($application);
$fileVolume = $application->fileStorages()->where('mount_path', '/app/data')->first();
expect($fileVolume->content)->toBeNull()
->and($fileVolume->is_directory)->toBeTrue();
});
it('preserves existing service file volume content when reparsing compose bind mounts', function () {
[$service, $serviceApplication] = makeComposeService(TWO_FILE_COMPOSE);
$baseDir = service_configuration_dir()."/{$service->uuid}";
seedFileVolume($serviceApplication, $baseDir, 'wg_config.conf', '/app/ps/wg0.conf', 'test-conf');
seedFileVolume($serviceApplication, $baseDir, 'override_trays.json', '/app/ps/override_tray.json', '0');
serviceParser($service);
$fileVolume = $serviceApplication->fileStorages()->where('mount_path', '/app/ps/override_tray.json')->first();
expect($fileVolume->content)->toBe('0')
->and($fileVolume->is_directory)->toBeFalse();
});
it('keeps existing service file volumes as files when content is empty', function () {
[$service, $serviceApplication] = makeComposeService(TWO_FILE_COMPOSE);
$baseDir = service_configuration_dir()."/{$service->uuid}";
seedFileVolume($serviceApplication, $baseDir, 'wg_config.conf', '/app/ps/wg0.conf', 'test-conf');
seedFileVolume($serviceApplication, $baseDir, 'override_trays.json', '/app/ps/override_tray.json', '');
serviceParser($service);
$fileVolume = $serviceApplication->fileStorages()->where('mount_path', '/app/ps/override_tray.json')->first();
expect($fileVolume->content)->toBe('')
->and($fileVolume->is_directory)->toBeFalse();
});
it('defaults new service bind mounts to directories', function () {
[$service, $serviceApplication] = makeComposeService(DATA_DIR_COMPOSE);
serviceParser($service);
$fileVolume = $serviceApplication->fileStorages()->where('mount_path', '/app/data')->first();
expect($fileVolume->content)->toBeNull()
->and($fileVolume->is_directory)->toBeTrue();
});