Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions src/openenv/core/containers/runtime/_server_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,45 @@ def parse_openenv_app_field(yaml_content: str) -> str | None:
def parse_dockerfile_cmd(dockerfile_content: str) -> str | None:
"""Extract the server command from the last Dockerfile ``CMD``."""
last_cmd: str | None = None
instruction = ""
escape = "\\"
directives = True
for line in dockerfile_content.splitlines():
stripped = line.strip()
stripped = line.lstrip()
if stripped.startswith("#"):
if directives:
directive = re.fullmatch(
r"#\s*(syntax|check|escape)\s*=\s*(.+)",
stripped,
flags=re.IGNORECASE,
)
if directive is None:
directives = False
elif directive[1].lower() == "escape" and directive[2].strip() in (
"\\",
"`",
):
escape = directive[2].strip()
continue
match = re.match(r"CMD\s+(.+)", stripped, flags=re.IGNORECASE)
if not stripped.strip():
directives = False
continue
directives = False
if not instruction:
match = re.match(r"\s*CMD\s+(.+)", line, flags=re.IGNORECASE)
if match:
last_cmd = match.group(1).strip()
continuation = re.search(
rf"(?<!{re.escape(escape)}){re.escape(escape)}[ \t]*$", line
)
if continuation:
instruction += line[: continuation.start()]
continue
instruction += line
match = re.match(r"\s*CMD\s+(.+)", instruction, flags=re.IGNORECASE)
if match:
last_cmd = match.group(1).strip()
instruction = ""

if last_cmd is None:
return None
Expand Down
55 changes: 55 additions & 0 deletions tests/test_core/test_server_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# SPDX-License-Identifier: BSD-3-Clause

"""Test the text parser without importing optional container/runtime packages."""

import importlib.util
import unittest
from pathlib import Path


_MODULE_PATH = (
Path(__file__).resolve().parents[2]
/ "src/openenv/core/containers/runtime/_server_config.py"
)
_SPEC = importlib.util.spec_from_file_location("openenv_server_config", _MODULE_PATH)
_MODULE = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(_MODULE)


class TestDockerfileCmd(unittest.TestCase):
def test_logical_instructions(self):
cases = [
("CMD uvicorn app:app\n", "uvicorn app:app"),
('CMD ["uvicorn", "app:app"]\n', "uvicorn app:app"),
("CMD first\nCMD second\n", "second"),
("# CMD ignored\ncmd real\n", "real"),
("FROM python:3.12\nCMD \n", None),
("CMD [not valid json\n", "[not valid json"),
("CMD unfinished \\", "unfinished \\"),
("CMD uvicorn \\\n app:app\n", "uvicorn app:app"),
('CMD ["uvicorn", \\\n "app:app"]\n', "uvicorn app:app"),
("CMD uvicorn \\\n# comment\n app:app\n", "uvicorn app:app"),
("CMD uvicorn \\\n\n app:app\n", "uvicorn app:app"),
("CMD printf hel\\\nlo\n", "printf hello"),
("CMD printf hel\\\r\nlo\r\n", "printf hello"),
("CMD printf hel\\ \t\nlo\n", "printf hello"),
("RUN echo \\\nCMD ignored\n", None),
("CMD first\nRUN echo \\\nCMD ignored\n", "first"),
("CMD first\nCMD printf \\\\ \nRUN echo next\n", "printf \\\\"),
("# escape=`\nCMD printf hel`\nlo\n", "printf hello"),
(
"# syntax=docker/dockerfile:1\n# escape=`\nCMD printf hel`\nlo\n",
"printf hello",
),
("# comment\n# escape=`\nCMD printf hel`\n", "printf hel`"),
("\n# escape=`\nCMD printf hel`\n", "printf hel`"),
('CMD printf "hello \\\nworld"\n', 'printf "hello world"'),
('CMD echo "# text"\n', 'echo "# text"'),
]
for dockerfile, expected in cases:
with self.subTest(dockerfile=dockerfile):
self.assertEqual(_MODULE.parse_dockerfile_cmd(dockerfile), expected)


if __name__ == "__main__":
unittest.main()