#!/usr/bin/env python3 """ Automated Docker Build and Compose Test Script Tests that each service can be built correctly and docker-compose starts without errors """ import os import sys import subprocess import time import json from pathlib import Path def run_command(cmd, cwd=None, timeout=300, capture_output=True): """Run a shell command with timeout and error handling""" try: print(f"Running: {cmd}") if capture_output: result = subprocess.run( cmd, shell=True, cwd=cwd, timeout=timeout, capture_output=True, text=True ) else: result = subprocess.run( cmd, shell=True, cwd=cwd, timeout=timeout ) return result except subprocess.TimeoutExpired: print(f"Command timed out after {timeout} seconds: {cmd}") return None except Exception as e: print(f"Error running command: {e}") return None def check_docker_available(): """Check if Docker is available and running""" print("🐳 Checking Docker availability...") # Check if docker command exists result = run_command("which docker") if result.returncode != 0: print("āŒ Docker command not found. Please install Docker.") return False # Check if Docker daemon is running result = run_command("docker version") if result.returncode != 0: print("āŒ Docker daemon is not running. Please start Docker.") return False # Check if docker-compose is available result = run_command("docker compose version") if result.returncode != 0: # Try legacy docker-compose result = run_command("docker-compose version") if result.returncode != 0: print("āŒ docker-compose not found. Please install docker-compose.") return False else: print("āœ… Using legacy docker-compose") else: print("āœ… Using Docker Compose v2") print("āœ… Docker is available and running") return True def test_docker_compose_config(): """Test docker-compose configuration""" print("\nšŸ“‹ Testing docker-compose configuration...") # Check if docker-compose.yml exists if not os.path.exists("docker-compose.yml"): print("āŒ docker-compose.yml not found") return False # Check if .env file exists if not os.path.exists(".env"): print("āŒ .env file not found") return False # Validate docker-compose configuration result = run_command("docker compose config") if result.returncode != 0: # Try legacy docker-compose result = run_command("docker-compose config") if result.returncode != 0: print("āŒ docker-compose configuration validation failed") if result.stderr: print(f"Error: {result.stderr}") return False print("āœ… docker-compose configuration is valid") return True def test_dockerfile_syntax(): """Test that each Dockerfile has valid syntax""" print("\nšŸ“„ Testing Dockerfile syntax...") services = [ ("auth", "services/auth/Dockerfile"), ("tenant", "services/tenant/Dockerfile"), ("training", "services/training/Dockerfile"), ("forecasting", "services/forecasting/Dockerfile"), ("data", "services/data/Dockerfile"), ("notification", "services/notification/Dockerfile") ] dockerfile_results = {} for service_name, dockerfile_path in services: print(f"\n--- Checking {service_name} Dockerfile ---") if not os.path.exists(dockerfile_path): print(f"āŒ Dockerfile not found: {dockerfile_path}") dockerfile_results[service_name] = False continue # Check Dockerfile syntax using docker build --dry-run (if available) # Otherwise just check if file exists and is readable try: with open(dockerfile_path, 'r') as f: content = f.read() if 'FROM' not in content: print(f"āŒ {service_name} Dockerfile missing FROM instruction") dockerfile_results[service_name] = False elif 'WORKDIR' not in content: print(f"āš ļø {service_name} Dockerfile missing WORKDIR instruction") dockerfile_results[service_name] = True else: print(f"āœ… {service_name} Dockerfile syntax looks good") dockerfile_results[service_name] = True except Exception as e: print(f"āŒ Error reading {service_name} Dockerfile: {e}") dockerfile_results[service_name] = False return dockerfile_results def check_requirements_files(): """Check that requirements.txt files exist for each service""" print("\nšŸ“¦ Checking requirements.txt files...") services = ["auth", "tenant", "training", "forecasting", "data", "notification"] requirements_results = {} for service in services: req_path = f"services/{service}/requirements.txt" if os.path.exists(req_path): print(f"āœ… {service} requirements.txt found") requirements_results[service] = True else: print(f"āŒ {service} requirements.txt missing") requirements_results[service] = False return requirements_results def test_service_main_files(): """Check that each service has a main.py file""" print("\nšŸ Checking service main.py files...") services = ["auth", "tenant", "training", "forecasting", "data", "notification"] main_file_results = {} for service in services: main_path = f"services/{service}/app/main.py" if os.path.exists(main_path): try: with open(main_path, 'r') as f: content = f.read() if 'FastAPI' in content or 'app = ' in content: print(f"āœ… {service} main.py looks good") main_file_results[service] = True else: print(f"āš ļø {service} main.py exists but may not be a FastAPI app") main_file_results[service] = True except Exception as e: print(f"āŒ Error reading {service} main.py: {e}") main_file_results[service] = False else: print(f"āŒ {service} main.py missing") main_file_results[service] = False return main_file_results def quick_build_test(): """Quick test to see if one service can build (faster than full build)""" print("\n⚔ Quick build test (data service only)...") # Test building just the data service cmd = "docker build --no-cache -t bakery/data-service:quick-test -f services/data/Dockerfile ." result = run_command(cmd, timeout=600) if result and result.returncode == 0: print("āœ… Data service quick build successful") # Cleanup run_command("docker rmi bakery/data-service:quick-test", timeout=30) return True else: print("āŒ Data service quick build failed") if result and result.stderr: print(f"Build error: {result.stderr[-1000:]}") # Last 1000 chars return False def main(): """Main test function""" print("šŸ” AUTOMATED DOCKER BUILD AND COMPOSE TESTING") print("=" * 60) base_path = Path(__file__).parent os.chdir(base_path) # Test results test_results = { "docker_available": False, "compose_config": False, "dockerfile_syntax": {}, "requirements_files": {}, "main_files": {}, "quick_build": False } # Check Docker availability test_results["docker_available"] = check_docker_available() if not test_results["docker_available"]: print("\nāŒ Docker tests cannot proceed without Docker") return 1 # Test docker-compose configuration test_results["compose_config"] = test_docker_compose_config() # Test Dockerfile syntax test_results["dockerfile_syntax"] = test_dockerfile_syntax() # Check requirements files test_results["requirements_files"] = check_requirements_files() # Check main.py files test_results["main_files"] = test_service_main_files() # Quick build test test_results["quick_build"] = quick_build_test() # Print final results print("\n" + "=" * 60) print("šŸ“‹ TEST RESULTS SUMMARY") print("=" * 60) print(f"Docker Available: {'āœ…' if test_results['docker_available'] else 'āŒ'}") print(f"Docker Compose Config: {'āœ…' if test_results['compose_config'] else 'āŒ'}") # Dockerfile syntax results dockerfile_success = all(test_results["dockerfile_syntax"].values()) print(f"Dockerfile Syntax: {'āœ…' if dockerfile_success else 'āŒ'}") for service, success in test_results["dockerfile_syntax"].items(): status = "āœ…" if success else "āŒ" print(f" - {service}: {status}") # Requirements files results req_success = all(test_results["requirements_files"].values()) print(f"Requirements Files: {'āœ…' if req_success else 'āŒ'}") for service, success in test_results["requirements_files"].items(): status = "āœ…" if success else "āŒ" print(f" - {service}: {status}") # Main files results main_success = all(test_results["main_files"].values()) print(f"Main.py Files: {'āœ…' if main_success else 'āŒ'}") for service, success in test_results["main_files"].items(): status = "āœ…" if success else "āŒ" print(f" - {service}: {status}") print(f"Quick Build Test: {'āœ…' if test_results['quick_build'] else 'āŒ'}") # Determine overall success overall_success = ( test_results["docker_available"] and test_results["compose_config"] and dockerfile_success and req_success and main_success and test_results["quick_build"] ) if overall_success: print("\nšŸŽ‰ ALL TESTS PASSED - Services should build and start correctly!") print("šŸ’” You can now run: docker compose up -d") return 0 else: print("\nāŒ SOME TESTS FAILED - Please fix issues before running docker compose") return 1 if __name__ == "__main__": exit_code = main() sys.exit(exit_code)