e20d94d6ba
Frontend: - Remove 4 console.log statements from terminal.tsx that flooded the browser console with WebSocket traffic (open, received X bytes, sending Y, xterm focused) Backend: - Downgrade Dockerfile/entrypoint compilation logs from INFO to DEBUG in _prepare_manifest_instance - Remove hex-dump diagnostic logging from docker_build.py (was for troubleshooting the backslash continuation bug, now fixed) - Downgrade Dockerfile write log from INFO to DEBUG
85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
"""Docker build service for building images from Dockerfiles."""
|
|
|
|
import logging
|
|
import subprocess
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
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.
|
|
|
|
Args:
|
|
instance_dir: Directory containing the Dockerfile
|
|
dockerfile: Dockerfile content
|
|
tag: Image tag to apply
|
|
build_context: Optional build context files {path: content}
|
|
|
|
Returns:
|
|
Tuple of (returncode, stdout, stderr)
|
|
"""
|
|
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
|
|
dockerfile_path = Path(instance_dir) / "Dockerfile"
|
|
dockerfile_path.write_text(dockerfile, newline="\n")
|
|
logger.debug("Wrote Dockerfile to %s (%d bytes)", dockerfile_path, len(dockerfile))
|
|
|
|
# Write build context files
|
|
if build_context:
|
|
for file_path, content in build_context.items():
|
|
full_path = Path(instance_dir) / file_path
|
|
# Security: ensure path doesn't escape instance_dir
|
|
try:
|
|
full_path.resolve().relative_to(Path(instance_dir).resolve())
|
|
except ValueError:
|
|
logger.error(
|
|
"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)
|
|
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)
|
|
|
|
# Build image
|
|
logger.debug("Building Docker image with tag: %s", tag)
|
|
cmd = [
|
|
"docker",
|
|
"build",
|
|
"-t",
|
|
tag,
|
|
"-f",
|
|
str(dockerfile_path),
|
|
instance_dir,
|
|
]
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=300, # 5 minute timeout for builds
|
|
)
|
|
logger.debug("Docker build completed: returncode=%d", result.returncode)
|
|
if result.returncode != 0:
|
|
logger.error("Docker build failed: %s", result.stderr[:1000])
|
|
return result.returncode, result.stdout, result.stderr
|
|
except subprocess.TimeoutExpired:
|
|
logger.error("Docker build timed out after 300 seconds")
|
|
return 1, "", "Build timed out after 300 seconds"
|
|
except Exception as exc:
|
|
logger.exception("Docker build failed: %s", exc)
|
|
return 1, "", str(exc)
|