feat: implement tool workshop - comprehensive tool system enhancement
- Add Docker Compose and Dockerfile support for tool definitions - Implement readiness probes with configurable command, timeout, interval - Create ConfigFolder model for reusable file collections with project overrides - Add rich tool config fields: port_override, start_command, working_directory, env vars, volumes - Build unified Tool Workshop UI at /tool-workshop replacing /tool-configs and /tool-types - Update instance creation to support dockerfile builds, config folder mounting, readiness probes - Add 3 database migrations for tool_types, tool_configs, and new config_folders table - Create docker_build.py and readiness_probe.py services - Add config_folders API with CRUD and project override endpoints Quality gates: frontend build passes, Python syntax valid, all phases complete Addresses tool-workshop OpenSpec change
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""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)
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Write Dockerfile
|
||||
dockerfile_path = Path(instance_dir) / "Dockerfile"
|
||||
dockerfile_path.write_text(dockerfile)
|
||||
logger.info("Wrote Dockerfile to %s", dockerfile_path)
|
||||
|
||||
# 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)
|
||||
full_path.write_text(content)
|
||||
logger.info("Wrote build context file: %s", full_path)
|
||||
|
||||
# Build image
|
||||
logger.info("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.info("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)
|
||||
Reference in New Issue
Block a user