Merakit-Deploy/wordpress/deploy.py

203 lines
6.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Production-ready WordPress deployment script
Combines environment generation and deployment with:
- Configuration validation
- Rollback capability
- Dry-run mode
- Monitoring hooks
"""
import argparse
import logging
import sys
from pathlib import Path
from typing import NoReturn
from rich.console import Console
from rich.logging import RichHandler
from wordpress_deployer.config import ConfigurationError, DeploymentConfig
from wordpress_deployer.orchestrator import DeploymentError, DeploymentOrchestrator
console = Console()
def setup_logging(log_level: str) -> None:
"""
Setup rich logging with colored output
Args:
log_level: Logging level (DEBUG, INFO, WARNING, ERROR)
"""
logging.basicConfig(
level=log_level.upper(),
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler(console=console, rich_tracebacks=True, show_path=False)]
)
# Reduce noise from urllib3/requests
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("requests").setLevel(logging.WARNING)
def parse_args() -> argparse.Namespace:
"""
Parse CLI arguments
Returns:
argparse.Namespace with parsed arguments
"""
parser = argparse.ArgumentParser(
description="Deploy WordPress with automatic environment generation",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Normal deployment
./deploy.py
# Dry-run mode (preview only)
./deploy.py --dry-run
# With webhook notifications
./deploy.py --webhook-url https://hooks.slack.com/xxx
# Debug mode
./deploy.py --log-level DEBUG
# Custom retry count
./deploy.py --max-retries 5
Environment Variables:
CLOUDFLARE_API_TOKEN Cloudflare API token (required)
CLOUDFLARE_ZONE_ID Cloudflare zone ID (required)
DEPLOYMENT_WEBHOOK_URL Webhook URL for notifications (optional)
DEPLOYMENT_MAX_RETRIES Max retries for DNS conflicts (default: 3)
For more information, see the documentation at:
/infra/templates/wordpress/README.md
"""
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Preview deployment without making changes"
)
parser.add_argument(
"--env-file",
type=Path,
default=Path(".env"),
help="Path to .env file (default: .env)"
)
parser.add_argument(
"--compose-file",
type=Path,
default=Path("docker-compose.yml"),
help="Path to docker-compose.yml (default: docker-compose.yml)"
)
parser.add_argument(
"--max-retries",
type=int,
default=3,
help="Max retries for DNS conflicts (default: 3)"
)
parser.add_argument(
"--webhook-url",
type=str,
help="Webhook URL for deployment notifications"
)
parser.add_argument(
"--log-level",
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
default="INFO",
help="Logging level (default: INFO)"
)
parser.add_argument(
"--no-verify-ssl",
action="store_true",
help="Skip SSL verification for health checks (not recommended for production)"
)
return parser.parse_args()
def print_banner() -> None:
"""Print deployment banner"""
console.print("\n[bold cyan]╔══════════════════════════════════════════════╗[/bold cyan]")
console.print("[bold cyan]║[/bold cyan] [bold white]WordPress Production Deployment[/bold white] [bold cyan]║[/bold cyan]")
console.print("[bold cyan]╚══════════════════════════════════════════════╝[/bold cyan]\n")
def main() -> NoReturn:
"""
Main entry point
Exit codes:
0: Success
1: Deployment failure
130: User interrupt (Ctrl+C)
"""
args = parse_args()
setup_logging(args.log_level)
logger = logging.getLogger(__name__)
print_banner()
try:
# Load configuration
logger.debug("Loading configuration...")
config = DeploymentConfig.from_env_and_args(args)
config.validate()
logger.debug("Configuration loaded successfully")
if config.dry_run:
console.print("[bold yellow]━━━ DRY-RUN MODE: No changes will be made ━━━[/bold yellow]\n")
# Create orchestrator and deploy
orchestrator = DeploymentOrchestrator(config)
orchestrator.deploy()
console.print("\n[bold green]╔══════════════════════════════════════════════╗[/bold green]")
console.print("[bold green]║[/bold green] [bold white]✓ Deployment Successful![/bold white] [bold green]║[/bold green]")
console.print("[bold green]╚══════════════════════════════════════════════╝[/bold green]\n")
sys.exit(0)
except ConfigurationError as e:
logger.error(f"Configuration error: {e}")
console.print(f"\n[bold red]✗ Configuration error: {e}[/bold red]\n")
console.print("[yellow]Please check your environment variables and configuration.[/yellow]")
console.print("[yellow]Required: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ZONE_ID[/yellow]\n")
sys.exit(1)
except DeploymentError as e:
logger.error(f"Deployment failed: {e}")
console.print(f"\n[bold red]✗ Deployment failed: {e}[/bold red]\n")
sys.exit(1)
except KeyboardInterrupt:
logger.warning("Deployment interrupted by user")
console.print("\n[bold yellow]✗ Deployment interrupted by user[/bold yellow]\n")
sys.exit(130)
except Exception as e:
logger.exception("Unexpected error")
console.print(f"\n[bold red]✗ Unexpected error: {e}[/bold red]\n")
console.print("[yellow]Please check the logs above for more details.[/yellow]\n")
sys.exit(1)
if __name__ == "__main__":
main()