From cde528bf5eb3c1cf01c27ea9ed14a566d2b8bc31 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:54:44 +0200 Subject: [PATCH] fix(parsers): enhance volume string handling by preserving mode in application and service parsers. Update related unit tests for validation. --- bootstrap/helpers/parsers.php | 24 ++++++++++++++++ .../project/shared/storages/all.blade.php | 3 +- tests/Unit/ParseDockerVolumeStringTest.php | 28 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php index 0fadaf7c7..649b87212 100644 --- a/bootstrap/helpers/parsers.php +++ b/bootstrap/helpers/parsers.php @@ -551,8 +551,14 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int if ($type->value() === 'bind') { if ($source->value() === '/var/run/docker.sock') { $volume = $source->value().':'.$target->value(); + if (isset($parsed['mode']) && $parsed['mode']) { + $volume .= ':'.$parsed['mode']->value(); + } } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { $volume = $source->value().':'.$target->value(); + if (isset($parsed['mode']) && $parsed['mode']) { + $volume .= ':'.$parsed['mode']->value(); + } } else { if ((int) $resource->compose_parsing_version >= 4) { $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); @@ -586,6 +592,9 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int } } $volume = "$source:$target"; + if (isset($parsed['mode']) && $parsed['mode']) { + $volume .= ':'.$parsed['mode']->value(); + } } } elseif ($type->value() === 'volume') { if ($topLevel->get('volumes')->has($source->value())) { @@ -609,6 +618,9 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int $target = $parsed['target']; $source = $name; $volume = "$source:$target"; + if (isset($parsed['mode']) && $parsed['mode']) { + $volume .= ':'.$parsed['mode']->value(); + } } elseif (is_array($volume)) { data_set($volume, 'source', $name); } @@ -1562,8 +1574,14 @@ function serviceParser(Service $resource): Collection if ($type->value() === 'bind') { if ($source->value() === '/var/run/docker.sock') { $volume = $source->value().':'.$target->value(); + if (isset($parsed['mode']) && $parsed['mode']) { + $volume .= ':'.$parsed['mode']->value(); + } } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { $volume = $source->value().':'.$target->value(); + if (isset($parsed['mode']) && $parsed['mode']) { + $volume .= ':'.$parsed['mode']->value(); + } } else { if ((int) $resource->compose_parsing_version >= 4) { $mainDirectory = str(base_configuration_dir().'/services/'.$uuid); @@ -1594,6 +1612,9 @@ function serviceParser(Service $resource): Collection } } $volume = "$source:$target"; + if (isset($parsed['mode']) && $parsed['mode']) { + $volume .= ':'.$parsed['mode']->value(); + } } } elseif ($type->value() === 'volume') { if ($topLevel->get('volumes')->has($source->value())) { @@ -1614,6 +1635,9 @@ function serviceParser(Service $resource): Collection $target = $parsed['target']; $source = $name; $volume = "$source:$target"; + if (isset($parsed['mode']) && $parsed['mode']) { + $volume .= ':'.$parsed['mode']->value(); + } } elseif (is_array($volume)) { data_set($volume, 'source', $name); } diff --git a/resources/views/livewire/project/shared/storages/all.blade.php b/resources/views/livewire/project/shared/storages/all.blade.php index f7bd7bdce..bd377e3b2 100644 --- a/resources/views/livewire/project/shared/storages/all.blade.php +++ b/resources/views/livewire/project/shared/storages/all.blade.php @@ -6,7 +6,8 @@ :resource="$resource" :isFirst="$loop->first" isReadOnly='true' isService='true' /> @else @endif @endforeach diff --git a/tests/Unit/ParseDockerVolumeStringTest.php b/tests/Unit/ParseDockerVolumeStringTest.php index 3b993efb5..6d31725e3 100644 --- a/tests/Unit/ParseDockerVolumeStringTest.php +++ b/tests/Unit/ParseDockerVolumeStringTest.php @@ -191,3 +191,31 @@ test('parses complex real-world examples', function () { expect($result['target']->value())->toBe('/var/lib/app-data'); expect($result['mode'])->toBeNull(); }); + +test('preserves mode when reconstructing volume strings', function () { + // Test cases that specifically verify mode preservation + $testCases = [ + '/var/run/docker.sock:/var/run/docker.sock:ro' => ['source' => '/var/run/docker.sock', 'target' => '/var/run/docker.sock', 'mode' => 'ro'], + '/etc/localtime:/etc/localtime:ro' => ['source' => '/etc/localtime', 'target' => '/etc/localtime', 'mode' => 'ro'], + '/tmp:/tmp:rw' => ['source' => '/tmp', 'target' => '/tmp', 'mode' => 'rw'], + 'gitea-data:/data:ro' => ['source' => 'gitea-data', 'target' => '/data', 'mode' => 'ro'], + './config:/app/config:cached' => ['source' => './config', 'target' => '/app/config', 'mode' => 'cached'], + 'volume:/data:delegated' => ['source' => 'volume', 'target' => '/data', 'mode' => 'delegated'], + ]; + + foreach ($testCases as $input => $expected) { + $result = parseDockerVolumeString($input); + + // Verify parsing + expect($result['source']->value())->toBe($expected['source']); + expect($result['target']->value())->toBe($expected['target']); + expect($result['mode']->value())->toBe($expected['mode']); + + // Verify reconstruction would preserve the mode + $reconstructed = $result['source']->value().':'.$result['target']->value(); + if ($result['mode']) { + $reconstructed .= ':'.$result['mode']->value(); + } + expect($reconstructed)->toBe($input); + } +});