Spaces:
Running
Running
| """ | |
| 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) | |
| 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)}", | |
| ) | |
| 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)}", | |
| ) | |
| 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)}" | |
| ) | |
| 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)}" | |
| ) | |