From f89b86491b9bb6d54a9b1cc4c3b9f08cb828feef Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Mon, 3 Nov 2025 13:44:06 +0000 Subject: [PATCH 1/2] feat: custom docker entrypoint --- bootstrap/helpers/docker.php | 25 +++++++++++ tests/Feature/DockerCustomCommandsTest.php | 49 ++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index d6c9b5bdf..93d37460e 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -942,6 +942,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) '--shm-size' => 'shm_size', '--gpus' => 'gpus', '--hostname' => 'hostname', + '--entrypoint' => 'entrypoint', ]); foreach ($matches as $match) { $option = $match[1]; @@ -962,6 +963,24 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) $options[$option] = array_unique($options[$option]); } } + if ($option === '--entrypoint') { + // Match --entrypoint=value or --entrypoint value + // Handle quoted strings: --entrypoint "sh -c 'command'" or --entrypoint='command' + // Try double quotes first, then single quotes, then unquoted + if (preg_match('/--entrypoint(?:=|\s+)"([^"]+)"/', $custom_docker_run_options, $entrypoint_matches)) { + $value = $entrypoint_matches[1]; + } elseif (preg_match("/--entrypoint(?:=|\s+)'([^']+)'/", $custom_docker_run_options, $entrypoint_matches)) { + $value = $entrypoint_matches[1]; + } elseif (preg_match('/--entrypoint(?:=|\s+)([^\s]+)/', $custom_docker_run_options, $entrypoint_matches)) { + $value = $entrypoint_matches[1]; + } else { + $value = null; + } + if ($value && ! empty(trim($value))) { + $options[$option][] = $value; + $options[$option] = array_unique($options[$option]); + } + } if (isset($match[2]) && $match[2] !== '') { $value = $match[2]; $options[$option][] = $value; @@ -1002,6 +1021,12 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) if (! is_null($value) && is_array($value) && count($value) > 0 && ! empty(trim($value[0]))) { $compose_options->put($mapping[$option], $value[0]); } + } elseif ($option === '--entrypoint') { + if (! is_null($value) && is_array($value) && count($value) > 0 && ! empty(trim($value[0]))) { + // Docker compose accepts entrypoint as either a string or an array + // Keep it as a string for simplicity - docker compose will handle it + $compose_options->put($mapping[$option], $value[0]); + } } elseif ($option === '--gpus') { $payload = [ 'driver' => 'nvidia', diff --git a/tests/Feature/DockerCustomCommandsTest.php b/tests/Feature/DockerCustomCommandsTest.php index a7829a534..b45a0c3db 100644 --- a/tests/Feature/DockerCustomCommandsTest.php +++ b/tests/Feature/DockerCustomCommandsTest.php @@ -125,3 +125,52 @@ ], ]); }); + +test('ConvertEntrypointSimple', function () { + $input = '--entrypoint /bin/sh'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => '/bin/sh', + ]); +}); + +test('ConvertEntrypointWithEquals', function () { + $input = '--entrypoint=/bin/bash'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => '/bin/bash', + ]); +}); + +test('ConvertEntrypointWithArguments', function () { + $input = '--entrypoint "sh -c npm install"'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => 'sh -c npm install', + ]); +}); + +test('ConvertEntrypointWithSingleQuotes', function () { + $input = "--entrypoint 'memcached -m 256'"; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => 'memcached -m 256', + ]); +}); + +test('ConvertEntrypointWithOtherOptions', function () { + $input = '--entrypoint /bin/bash --cap-add SYS_ADMIN --privileged'; + $output = convertDockerRunToCompose($input); + expect($output)->toHaveKeys(['entrypoint', 'cap_add', 'privileged']) + ->and($output['entrypoint'])->toBe('/bin/bash') + ->and($output['cap_add'])->toBe(['SYS_ADMIN']) + ->and($output['privileged'])->toBe(true); +}); + +test('ConvertEntrypointComplex', function () { + $input = '--entrypoint "sh -c \'npm install && npm start\'"'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => "sh -c 'npm install && npm start'", + ]); +}); From f5d549365c6b507c1ecb8cf55134853d0e02dcd2 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Mon, 3 Nov 2025 21:10:32 +0000 Subject: [PATCH 2/2] fix: handle escaped quotes in docker entrypoint parsing --- bootstrap/helpers/docker.php | 37 +++++++++++++++------- tests/Feature/DockerCustomCommandsTest.php | 24 ++++++++++++++ 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 93d37460e..d9aebe05b 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -965,21 +965,34 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null) } if ($option === '--entrypoint') { // Match --entrypoint=value or --entrypoint value - // Handle quoted strings: --entrypoint "sh -c 'command'" or --entrypoint='command' - // Try double quotes first, then single quotes, then unquoted - if (preg_match('/--entrypoint(?:=|\s+)"([^"]+)"/', $custom_docker_run_options, $entrypoint_matches)) { - $value = $entrypoint_matches[1]; - } elseif (preg_match("/--entrypoint(?:=|\s+)'([^']+)'/", $custom_docker_run_options, $entrypoint_matches)) { - $value = $entrypoint_matches[1]; - } elseif (preg_match('/--entrypoint(?:=|\s+)([^\s]+)/', $custom_docker_run_options, $entrypoint_matches)) { - $value = $entrypoint_matches[1]; - } else { - $value = null; + // Handle quoted strings with escaped quotes: --entrypoint "python -c \"print('hi')\"" + // Pattern matches: double-quoted (with escapes), single-quoted (with escapes), or unquoted values + if (preg_match( + '/--entrypoint(?:=|\s+)(?"(?:\\\\.|[^"])*"|\'(?:\\\\.|[^\'])*\'|[^\s]+)/', + $custom_docker_run_options, + $entrypoint_matches + )) { + $rawValue = $entrypoint_matches['raw']; + // Handle double-quoted strings: strip quotes and unescape special characters + if (str_starts_with($rawValue, '"') && str_ends_with($rawValue, '"')) { + $inner = substr($rawValue, 1, -1); + // Unescape backslash sequences: \" \$ \` \\ + $value = preg_replace('/\\\\(["$`\\\\])/', '$1', $inner); + } elseif (str_starts_with($rawValue, "'") && str_ends_with($rawValue, "'")) { + // Handle single-quoted strings: just strip quotes (no unescaping per shell rules) + $value = substr($rawValue, 1, -1); + } else { + // Handle unquoted values + $value = $rawValue; + } } - if ($value && ! empty(trim($value))) { + + if (isset($value) && trim($value) !== '') { $options[$option][] = $value; - $options[$option] = array_unique($options[$option]); + $options[$option] = array_values(array_unique($options[$option])); } + + continue; } if (isset($match[2]) && $match[2] !== '') { $value = $match[2]; diff --git a/tests/Feature/DockerCustomCommandsTest.php b/tests/Feature/DockerCustomCommandsTest.php index b45a0c3db..5d9dcd174 100644 --- a/tests/Feature/DockerCustomCommandsTest.php +++ b/tests/Feature/DockerCustomCommandsTest.php @@ -174,3 +174,27 @@ 'entrypoint' => "sh -c 'npm install && npm start'", ]); }); + +test('ConvertEntrypointWithEscapedDoubleQuotes', function () { + $input = '--entrypoint "python -c \"print(\'hi\')\""'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => "python -c \"print('hi')\"", + ]); +}); + +test('ConvertEntrypointWithEscapedSingleQuotesInDoubleQuotes', function () { + $input = '--entrypoint "sh -c \"echo \'hello\'\""'; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => "sh -c \"echo 'hello'\"", + ]); +}); + +test('ConvertEntrypointSingleQuotedWithDoubleQuotesInside', function () { + $input = '--entrypoint \'python -c "print(\"hi\")"\''; + $output = convertDockerRunToCompose($input); + expect($output)->toBe([ + 'entrypoint' => 'python -c "print(\"hi\")"', + ]); +});