300 lines
10 KiB
Python
300 lines
10 KiB
Python
|
|
#!/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)
|