#!/bin/bash # Cloudflare Infrastructure Deployment Automation # Automated Terraform deployment with safety checks and rollback capabilities set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration TERRAFORM_DIR="terraform" BACKUP_DIR="terraform_backups" STATE_FILE="terraform.tfstate" PLAN_FILE="deployment_plan.tfplan" LOG_FILE="deployment_$(date +%Y%m%d_%H%M%S).log" # Function to log messages log() { echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE" } # Function to log success success() { echo -e "${GREEN}✅ $1${NC}" | tee -a "$LOG_FILE" } # Function to log warning warning() { echo -e "${YELLOW}⚠️ $1${NC}" | tee -a "$LOG_FILE" } # Function to log error error() { echo -e "${RED}❌ $1${NC}" | tee -a "$LOG_FILE" exit 1 } # Function to check prerequisites check_prerequisites() { log "Checking prerequisites..." # Check if .env file exists if [[ ! -f "../.env" ]]; then error "Missing .env file. Run setup_credentials.sh first." fi # Source environment variables source "../.env" # Check required variables if [[ -z "$CLOUDFLARE_API_TOKEN" ]]; then error "CLOUDFLARE_API_TOKEN not set in .env" fi if [[ -z "$CLOUDFLARE_ACCOUNT_ID" ]]; then error "CLOUDFLARE_ACCOUNT_ID not set in .env" fi # Check Terraform installation if ! command -v terraform &> /dev/null; then error "Terraform not found. Please install Terraform first." fi # Check Terraform version TF_VERSION=$(terraform version | head -n1 | awk '{print $2}' | sed 's/v//') log "Terraform version: $TF_VERSION" success "Prerequisites check passed" } # Function to backup current state backup_state() { log "Creating backup of current state..." # Create backup directory mkdir -p "$BACKUP_DIR" # Backup state file if it exists if [[ -f "$STATE_FILE" ]]; then BACKUP_NAME="${BACKUP_DIR}/state_backup_$(date +%Y%m%d_%H%M%S).tfstate" cp "$STATE_FILE" "$BACKUP_NAME" success "State backed up to: $BACKUP_NAME" else warning "No existing state file found" fi # Backup terraform.tfvars if [[ -f "terraform.tfvars" ]]; then cp "terraform.tfvars" "${BACKUP_DIR}/terraform.tfvars.backup" fi } # Function to prepare terraform.tfvars prepare_config() { log "Preparing Terraform configuration..." # Update terraform.tfvars with actual credentials cat > terraform.tfvars << EOF cloudflare_api_token = "$CLOUDFLARE_API_TOKEN" cloudflare_account_id = "$CLOUDFLARE_ACCOUNT_ID" cloudflare_account_name = "" # Use account_id from .env EOF # Add optional Zone ID if set if [[ -n "$CLOUDFLARE_ZONE_ID" ]]; then echo "cloudflare_zone_id = \"$CLOUDFLARE_ZONE_ID\"" >> terraform.tfvars fi success "Configuration prepared" } # Function to initialize Terraform init_terraform() { log "Initializing Terraform..." if terraform init -upgrade; then success "Terraform initialized successfully" else error "Terraform initialization failed" fi } # Function to validate Terraform configuration validate_config() { log "Validating Terraform configuration..." if terraform validate; then success "Configuration validation passed" else error "Configuration validation failed" fi } # Function to create deployment plan create_plan() { log "Creating deployment plan..." if terraform plan -out="$PLAN_FILE" -detailed-exitcode; then case $? in 0) success "No changes needed" return 0 ;; 2) success "Plan created successfully" return 2 ;; *) error "Plan creation failed" ;; esac else error "Plan creation failed" fi } # Function to show plan summary show_plan_summary() { log "Plan Summary:" terraform show -json "$PLAN_FILE" | jq -r ' .resource_changes[] | select(.change.actions != ["no-op"]) | "\(.change.actions | join(",")) \(.type).\(.name)" ' | sort | tee -a "$LOG_FILE" } # Function to confirm deployment confirm_deployment() { echo echo "==================================================" echo "🚀 DEPLOYMENT CONFIRMATION" echo "==================================================" echo echo "The following changes will be applied:" show_plan_summary echo echo "Log file: $LOG_FILE" echo "Backup directory: $BACKUP_DIR" echo read -p "Do you want to proceed with deployment? (y/n): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then log "Deployment cancelled by user" exit 0 fi } # Function to apply deployment apply_deployment() { log "Applying deployment..." if terraform apply "$PLAN_FILE"; then success "Deployment applied successfully" else error "Deployment failed" fi } # Function to verify deployment verify_deployment() { log "Verifying deployment..." # Check if resources were created successfully OUTPUTS=$(terraform output -json) if [[ -n "$OUTPUTS" ]]; then success "Deployment verification passed" echo "Outputs:" terraform output else warning "No outputs generated - manual verification required" fi } # Function to cleanup temporary files cleanup() { log "Cleaning up temporary files..." if [[ -f "$PLAN_FILE" ]]; then rm "$PLAN_FILE" success "Plan file removed" fi } # Function to show deployment summary deployment_summary() { echo echo "==================================================" echo "🎉 DEPLOYMENT SUMMARY" echo "==================================================" echo echo "✅ Infrastructure deployed successfully" echo "📋 Log file: $LOG_FILE" echo "💾 Backups: $BACKUP_DIR" echo "🌐 Resources deployed:" terraform state list echo echo "Next steps:" echo "1. Check Cloudflare dashboard for deployed resources" echo "2. Test DNS resolution for your domains" echo "3. Verify WAF rules are active" echo "4. Test tunnel connectivity" echo } # Function to handle rollback rollback() { error "Deployment failed - rolling back..." # Check if we have a backup LATEST_BACKUP=$(ls -t "${BACKUP_DIR}/state_backup_*.tfstate" 2>/dev/null | head -n1) if [[ -n "$LATEST_BACKUP" ]]; then log "Restoring from backup: $LATEST_BACKUP" cp "$LATEST_BACKUP" "$STATE_FILE" warning "State restored from backup. Manual verification required." else error "No backup available for rollback" fi } # Main deployment function main() { echo "🚀 Cloudflare Infrastructure Deployment" echo "==================================================" echo # Change to Terraform directory cd "$TERRAFORM_DIR" || error "Terraform directory not found" # Set trap for cleanup on exit trap cleanup EXIT # Execute deployment steps check_prerequisites backup_state prepare_config init_terraform validate_config # Create plan and check if changes are needed if create_plan; then case $? in 0) success "No changes needed - infrastructure is up to date" exit 0 ;; 2) confirm_deployment apply_deployment verify_deployment deployment_summary ;; esac fi } # Handle errors trap 'rollback' ERR # Run main function main "$@"