deploymate / app /api /routes /docker.py
shakauthossain's picture
V2.0.0
2df0cf9 verified
"""
API routes for Docker file generation.
"""
import os
import uuid
from fastapi import APIRouter, Depends, HTTPException, Request, status
from app.api.deps import get_logger, get_settings
from app.core.security import limiter
from app.models.schemas import (
DockerfileConfig,
DockerComposeConfig,
DockerGeneratedFiles,
)
from app.services.docker_generator import DockerGenerator
from app.services.dockerfile_validator import DockerfileValidator
from app.core.config import settings
from app.core.logging import logger
import jinja2
router = APIRouter()
# Create Docker Generator instance
template_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(settings.templates_dir),
trim_blocks=True,
lstrip_blocks=True,
autoescape=True,
cache_size=0,
)
docker_generator = DockerGenerator(template_env, settings, logger)
dockerfile_validator = DockerfileValidator(logger)
@router.post("/generate-dockerfile", response_model=DockerGeneratedFiles)
@limiter.limit("10/minute")
async def generate_dockerfile(
request: Request,
config: DockerfileConfig,
settings=Depends(get_settings),
logger=Depends(get_logger),
):
"""Generate a Dockerfile based on stack type."""
client_ip = request.client.host if request.client else "unknown"
logger.info(f"Dockerfile generation request from {client_ip} for {config.stackType}")
try:
# Generate unique filename
file_id = str(uuid.uuid4())[:8]
# Generate Dockerfile
dockerfile_content = docker_generator.generate_dockerfile(config, file_id)
dockerfile_filename = f"Dockerfile-{file_id}"
dockerfile_path = settings.generated_dir / dockerfile_filename
# Ensure generated directory exists
dockerfile_path.parent.mkdir(exist_ok=True)
# Write Dockerfile
with open(dockerfile_path, "w") as f:
f.write(dockerfile_content)
# Generate .dockerignore
dockerignore_content = docker_generator.generate_dockerignore(config, file_id)
dockerignore_filename = f".dockerignore-{file_id}"
dockerignore_path = settings.generated_dir / dockerignore_filename
with open(dockerignore_path, "w") as f:
f.write(dockerignore_content)
# Create URLs
dockerfile_url = f"/download/{dockerfile_filename}"
dockerignore_url = f"/download/{dockerignore_filename}"
response = DockerGeneratedFiles(
dockerfile_url=dockerfile_url,
dockerfile_path=str(dockerfile_path),
dockerignore_url=dockerignore_url,
dockerignore_path=str(dockerignore_path),
description=f"Dockerfile for {config.stackType} "
f"({'multi-stage' if config.multiStage else 'single-stage'}, "
f"{'development' if config.developmentMode else 'production'})",
)
return response
except Exception as e:
logger.error(f"Failed to generate Dockerfile: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to generate Dockerfile: {str(e)}",
)
@router.post("/generate-docker-compose", response_model=DockerGeneratedFiles)
@limiter.limit("10/minute")
async def generate_docker_compose(
request: Request,
config: DockerComposeConfig,
settings=Depends(get_settings),
logger=Depends(get_logger),
):
"""Generate docker-compose.yml and supporting files."""
client_ip = request.client.host if request.client else "unknown"
logger.info(
f"Docker Compose generation request from {client_ip} for {config.stackType}"
)
try:
# Generate unique filename
file_id = str(uuid.uuid4())[:8]
# Generate docker-compose.yml
compose_content = docker_generator.generate_docker_compose(config, file_id)
compose_filename = f"docker-compose-{file_id}.yml"
compose_path = settings.generated_dir / compose_filename
# Ensure generated directory exists
compose_path.parent.mkdir(exist_ok=True)
# Write compose file
with open(compose_path, "w") as f:
f.write(compose_content)
response = DockerGeneratedFiles(
compose_url=f"/download/{compose_filename}",
compose_path=str(compose_path),
description=f"Docker Compose for {config.stackType} ({config.mode} mode)",
)
# Generate .dockerignore
dockerignore_content = docker_generator.generate_dockerignore(config, file_id)
dockerignore_filename = f".dockerignore-{file_id}"
dockerignore_path = settings.generated_dir / dockerignore_filename
with open(dockerignore_path, "w") as f:
f.write(dockerignore_content)
response.dockerignore_url = f"/download/{dockerignore_filename}"
response.dockerignore_path = str(dockerignore_path)
# Generate .env.example if requested
if config.includeEnvFile:
env_content = docker_generator.generate_env_example(config, file_id)
env_filename = f".env.example-{file_id}"
env_path = settings.generated_dir / env_filename
with open(env_path, "w") as f:
f.write(env_content)
response.env_url = f"/download/{env_filename}"
response.env_path = str(env_path)
# Generate build script
build_script_content = docker_generator.generate_build_script(config, file_id)
build_script_filename = f"docker-build-{file_id}.sh"
build_script_path = settings.generated_dir / build_script_filename
with open(build_script_path, "w") as f:
f.write(build_script_content)
# Make script executable
os.chmod(build_script_path, 0o755)
response.build_script_url = f"/download/{build_script_filename}"
response.build_script_path = str(build_script_path)
return response
except Exception as e:
logger.error(f"Failed to generate docker-compose: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to generate docker-compose: {str(e)}",
)
@router.get("/validate-dockerfile/{file_id}")
@limiter.limit("20/minute")
async def validate_dockerfile(
request: Request,
file_id: str,
logger=Depends(get_logger),
settings=Depends(get_settings),
):
"""
Validate a generated Dockerfile for syntax and best practices
"""
client_ip = request.client.host if request.client else "unknown"
logger.info(f"Dockerfile validation request from {client_ip} for file {file_id}")
try:
# Find the Dockerfile
dockerfile_path = settings.generated_dir / f"Dockerfile-{file_id}"
if not dockerfile_path.exists():
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Dockerfile not found: {file_id}"
)
# Read Dockerfile content
with open(dockerfile_path, 'r') as f:
dockerfile_content = f.read()
# Validate
validation_result = dockerfile_validator.validate_dockerfile(
dockerfile_content,
stack_type="unknown" # We could extract this from config if needed
)
logger.info(f"Validation result for {file_id}: valid={validation_result['valid']}, score={validation_result['score']}")
return validation_result
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to validate Dockerfile: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to validate Dockerfile: {str(e)}"
)
@router.get("/validate-docker-compose/{file_id}")
@limiter.limit("20/minute")
async def validate_docker_compose(
request: Request,
file_id: str,
logger=Depends(get_logger),
settings=Depends(get_settings),
):
"""
Validate a generated docker-compose.yml for syntax and best practices
"""
client_ip = request.client.host if request.client else "unknown"
logger.info(f"Docker Compose validation request from {client_ip} for file {file_id}")
try:
# Find the docker-compose file
compose_path = settings.generated_dir / f"docker-compose-{file_id}.yml"
if not compose_path.exists():
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Docker Compose file not found: {file_id}"
)
# Read compose content
with open(compose_path, 'r') as f:
compose_content = f.read()
# Validate
validation_result = dockerfile_validator.validate_docker_compose(compose_content)
logger.info(f"Validation result for {file_id}: valid={validation_result['valid']}, score={validation_result['score']}")
return validation_result
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to validate docker-compose: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to validate docker-compose: {str(e)}"
)