fix: normalise CRLF to LF in Docker build files
Docker's legacy builder treats \r as a literal character after a backslash continuation, breaking RUN multi-line commands and producing 'unknown instruction: curl' errors. Add defensive CRLF→LF normalisation for both Dockerfile and build context files before writing. Also log hex representation of first 8 lines so we can verify exactly what bytes Docker receives.
This commit is contained in:
@@ -1071,7 +1071,11 @@ async def _prepare_manifest_instance(
|
|||||||
dockerfile = compile_dockerfile(manifest)
|
dockerfile = compile_dockerfile(manifest)
|
||||||
entrypoint = compile_entrypoint(manifest)
|
entrypoint = compile_entrypoint(manifest)
|
||||||
|
|
||||||
logger.info("Compiled Dockerfile for instance %s (%d chars)", instance.id, len(dockerfile))
|
logger.info(
|
||||||
|
"Compiled Dockerfile for instance %s (%d chars)",
|
||||||
|
instance.id,
|
||||||
|
len(dockerfile),
|
||||||
|
)
|
||||||
logger.debug("Dockerfile content: %s", dockerfile)
|
logger.debug("Dockerfile content: %s", dockerfile)
|
||||||
logger.info("Compiled entrypoint for instance %s", instance.id)
|
logger.info("Compiled entrypoint for instance %s", instance.id)
|
||||||
logger.debug("Entrypoint content: %s", entrypoint)
|
logger.debug("Entrypoint content: %s", entrypoint)
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import subprocess
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def build_image(instance_dir: str, dockerfile: str, tag: str, build_context: dict | None = None) -> tuple[int, str, str]:
|
def build_image(
|
||||||
|
instance_dir: str, dockerfile: str, tag: str, build_context: dict | None = None
|
||||||
|
) -> tuple[int, str, str]:
|
||||||
"""Build a Docker image from a Dockerfile.
|
"""Build a Docker image from a Dockerfile.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -20,12 +22,23 @@ def build_image(instance_dir: str, dockerfile: str, tag: str, build_context: dic
|
|||||||
"""
|
"""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Defensive: normalise any CRLF that may have crept in from manifest DB
|
||||||
|
# strings — Docker's legacy builder treats \r as a character after the
|
||||||
|
# backslash, breaking RUN continuations and producing
|
||||||
|
# "unknown instruction" errors.
|
||||||
|
dockerfile = dockerfile.replace("\r\n", "\n").replace("\r", "\n")
|
||||||
|
|
||||||
# Write Dockerfile
|
# Write Dockerfile
|
||||||
dockerfile_path = Path(instance_dir) / "Dockerfile"
|
dockerfile_path = Path(instance_dir) / "Dockerfile"
|
||||||
dockerfile_path.write_text(dockerfile, newline="\n")
|
dockerfile_path.write_text(dockerfile, newline="\n")
|
||||||
logger.info("Wrote Dockerfile to %s (%d bytes)", dockerfile_path, len(dockerfile))
|
logger.info("Wrote Dockerfile to %s (%d bytes)", dockerfile_path, len(dockerfile))
|
||||||
logger.debug("Dockerfile content:\n%s", dockerfile)
|
logger.debug("Dockerfile content:\n%s", dockerfile)
|
||||||
|
|
||||||
|
# Hex-dump first 8 lines so we can see exactly what bytes Docker receives
|
||||||
|
lines_for_hex = dockerfile.split("\n")[:8]
|
||||||
|
for i, line in enumerate(lines_for_hex, start=1):
|
||||||
|
logger.info("Dockerfile line %d hex: %s", i, line.encode("utf-8").hex())
|
||||||
|
|
||||||
# Write build context files
|
# Write build context files
|
||||||
if build_context:
|
if build_context:
|
||||||
for file_path, content in build_context.items():
|
for file_path, content in build_context.items():
|
||||||
@@ -34,19 +47,27 @@ def build_image(instance_dir: str, dockerfile: str, tag: str, build_context: dic
|
|||||||
try:
|
try:
|
||||||
full_path.resolve().relative_to(Path(instance_dir).resolve())
|
full_path.resolve().relative_to(Path(instance_dir).resolve())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.error("Build context file path escapes instance directory: %s", file_path)
|
logger.error(
|
||||||
raise ValueError(f"Build context file path '{file_path}' escapes instance directory")
|
"Build context file path escapes instance directory: %s", file_path
|
||||||
|
)
|
||||||
|
raise ValueError(
|
||||||
|
f"Build context file path '{file_path}' escapes instance directory"
|
||||||
|
)
|
||||||
|
|
||||||
full_path.parent.mkdir(parents=True, exist_ok=True)
|
full_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
full_path.write_text(content, newline="\n")
|
normalized = content.replace("\r\n", "\n").replace("\r", "\n")
|
||||||
|
full_path.write_text(normalized, newline="\n")
|
||||||
logger.debug("Wrote build context file: %s", full_path)
|
logger.debug("Wrote build context file: %s", full_path)
|
||||||
|
|
||||||
# Build image
|
# Build image
|
||||||
logger.debug("Building Docker image with tag: %s", tag)
|
logger.debug("Building Docker image with tag: %s", tag)
|
||||||
cmd = [
|
cmd = [
|
||||||
"docker", "build",
|
"docker",
|
||||||
"-t", tag,
|
"build",
|
||||||
"-f", str(dockerfile_path),
|
"-t",
|
||||||
|
tag,
|
||||||
|
"-f",
|
||||||
|
str(dockerfile_path),
|
||||||
instance_dir,
|
instance_dir,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user