Files
headquarter/apps/api/src/services/build/docker_build.py
T
alex 37ccaa4fdc refactor: organize API routers and services into subpackages
Service organization (19 files moved into 6 subpackages):
- services/instance/ — event_bus, health_monitor, lifecycle_hooks
- services/config/ — config_profile_resolver
- services/git/ — clone, git_operations, git_service
- services/build/ — docker_build, manifest_compiler
- services/terminal/ — terminal_manager, terminal_session
- services/shared/ — correlation, file_service, notification_service,
  permission_fixer, readiness_probe, ssh_keys, tunnel, workspace_manager

API router organization (16 files moved into 6 subpackages):
- api/tool/ — tool_instances, tool_types, tool_definitions,
  tool_types_validation, sessions (extracted from tool_instances)
- api/config/ — config_profiles, user_config
- api/workspace/ — workspaces, workspace_files, workspace_git,
  workspace_instances
- api/user/ — users, auth, ssh_keys
- api/project/ — projects, git_repositories
- api/system/ — health, events, notifications, dashboard, terminal,
  instance_proxy

Updated main.py imports and all __init__.py re-exports.
Sessions router extracted from tool_instances.py into api/tool/sessions.py.

Quality gates: py_compile passed, ruff passed.
2026-06-04 12:24:14 +02:00

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)