Volver
Módulo 11 - Integración y Automatización EXPERTO

Automatización de Despliegues

Implementa sistemas de despliegue automatizado y CI/CD con Bash

La automatización de despliegues es fundamental en DevOps moderno. Bash proporciona la flexibilidad y potencia necesaria para crear pipelines de despliegue robustos que pueden manejar desde simples aplicaciones web hasta complejos sistemas distribuidos.

Flujo de Despliegue Típico

Pipeline de Despliegue Continuo:

1
Preparación: Validar entorno, verificar conectividad y prerequisitos
2
Build: Compilar código, ejecutar pruebas y generar artefactos
3
Backup: Respaldar versión actual antes del despliegue
4
Despliegue: Implementar nueva versión con estrategia definida
5
Verificación: Ejecutar health checks y pruebas de humo
6
Rollback: Revertir automáticamente si hay fallos críticos
7
Notificación: Informar resultado a equipos y stakeholders

Script de Despliegue Básico

Despliegue simple - deploy_basic.sh
bash
#!/bin/bash

# Script de despliegue básico para aplicaciones web
# Versión: 1.0

set -euo pipefail

# Configuración
APP_NAME="MiAplicacion"
APP_DIR="/var/www/miapp"
BACKUP_DIR="/var/backups/miapp"
REPO_URL="https://github.com/usuario/miapp.git"
BRANCH="${DEPLOY_BRANCH:-main}"
SERVICE_NAME="miapp"

# Colores
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'

# Variables de estado
DEPLOYMENT_ID="deploy_$(date +%Y%m%d_%H%M%S)"
DEPLOYMENT_LOG="/var/log/deployments/$DEPLOYMENT_ID.log"
ROLLBACK_AVAILABLE=false

# Crear directorio de logs
mkdir -p "$(dirname "$DEPLOYMENT_LOG")"

# Función de logging
log() {
    local level="$1"
    shift
    local message="$*"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    case "$level" in
        INFO)
            echo -e "${BLUE}[INFO]${NC} $message" | tee -a "$DEPLOYMENT_LOG"
            ;;
        SUCCESS)
            echo -e "${GREEN}[SUCCESS]${NC} $message" | tee -a "$DEPLOYMENT_LOG"
            ;;
        WARN)
            echo -e "${YELLOW}[WARN]${NC} $message" | tee -a "$DEPLOYMENT_LOG"
            ;;
        ERROR)
            echo -e "${RED}[ERROR]${NC} $message" | tee -a "$DEPLOYMENT_LOG"
            ;;
    esac
    
    echo "[$timestamp] [$level] $message" >> "$DEPLOYMENT_LOG"
}

# Banner del despliegue
show_deployment_banner() {
    clear
    echo -e "${BLUE}"
    cat << "EOF"
╔══════════════════════════════════════════════════════════════╗
║                    SISTEMA DE DESPLIEGUE                    ║
║                         v1.0                                ║
╚══════════════════════════════════════════════════════════════╝
EOF
    echo -e "${NC}"
    log INFO "Iniciando despliegue de $APP_NAME"
    log INFO "ID de despliegue: $DEPLOYMENT_ID"
    log INFO "Rama: $BRANCH"
}

# Verificar prerrequisitos
check_prerequisites() {
    log INFO "Verificando prerrequisitos..."
    
    # Verificar conectividad a repositorio
    if ! git ls-remote "$REPO_URL" &>/dev/null; then
        log ERROR "No se puede acceder al repositorio: $REPO_URL"
        return 1
    fi
    
    # Verificar espacio en disco (al menos 1GB libre)
    local available_space=$(df /var | tail -1 | awk '{print $4}')
    if [ "$available_space" -lt 1048576 ]; then
        log ERROR "Espacio insuficiente en disco"
        return 1
    fi
    
    # Verificar servicios necesarios
    local required_services=("nginx" "php-fpm")
    for service in "${required_services[@]}"; do
        if ! systemctl is-active "$service" &>/dev/null; then
            log WARN "Servicio no activo: $service"
        fi
    done
    
    log SUCCESS "Prerrequisitos verificados"
}

# Crear backup de la versión actual
create_backup() {
    log INFO "Creando backup de la versión actual..."
    
    if [ ! -d "$APP_DIR" ]; then
        log WARN "Directorio de aplicación no existe, omitiendo backup"
        return 0
    fi
    
    local backup_path="$BACKUP_DIR/$DEPLOYMENT_ID"
    mkdir -p "$backup_path"
    
    # Crear backup completo
    if tar -czf "$backup_path/app_backup.tar.gz" -C "$APP_DIR" . 2>/dev/null; then
        log SUCCESS "Backup creado: $backup_path/app_backup.tar.gz"
        ROLLBACK_AVAILABLE=true
        echo "$backup_path" > "/tmp/last_backup_$APP_NAME"
    else
        log ERROR "Error creando backup"
        return 1
    fi
    
    # Backup de base de datos si existe
    if command -v mysqldump &>/dev/null; then
        local db_name="${APP_NAME,,}"
        if mysql -e "USE $db_name;" &>/dev/null; then
            mysqldump "$db_name" > "$backup_path/database_backup.sql"
            log SUCCESS "Backup de base de datos creado"
        fi
    fi
}

# Descargar nueva versión
download_new_version() {
    log INFO "Descargando nueva versión desde $REPO_URL (rama: $BRANCH)..."
    
    local temp_dir=$(mktemp -d)
    
    # Clonar repositorio
    if git clone -b "$BRANCH" --single-branch "$REPO_URL" "$temp_dir" &>/dev/null; then
        log SUCCESS "Código descargado exitosamente"
    else
        log ERROR "Error descargando el código"
        rm -rf "$temp_dir"
        return 1
    fi
    
    # Obtener información de la versión
    cd "$temp_dir"
    local commit_hash=$(git rev-parse HEAD)
    local commit_message=$(git log -1 --pretty=format:"%s")
    local commit_author=$(git log -1 --pretty=format:"%an")
    local commit_date=$(git log -1 --pretty=format:"%ci")
    
    log INFO "Commit: $commit_hash"
    log INFO "Mensaje: $commit_message"
    log INFO "Autor: $commit_author"
    log INFO "Fecha: $commit_date"
    
    # Guardar información de versión
    cat > "/tmp/deploy_info_$APP_NAME" << EOF
DEPLOYMENT_ID=$DEPLOYMENT_ID
COMMIT_HASH=$commit_hash
COMMIT_MESSAGE=$commit_message
COMMIT_AUTHOR=$commit_author
COMMIT_DATE=$commit_date
DEPLOY_TIME=$(date)
EOF
    
    # Mover código al directorio temporal para despliegue
    mv "$temp_dir" "/tmp/deploy_$DEPLOYMENT_ID"
    log SUCCESS "Código preparado para despliegue"
}

# Ejecutar pruebas
run_tests() {
    log INFO "Ejecutando pruebas..."
    
    cd "/tmp/deploy_$DEPLOYMENT_ID"
    
    # Verificar si hay archivo de pruebas
    if [ -f "package.json" ]; then
        if command -v npm &>/dev/null; then
            log INFO "Ejecutando pruebas npm..."
            if npm test &>/dev/null; then
                log SUCCESS "Pruebas npm pasaron"
            else
                log ERROR "Pruebas npm fallaron"
                return 1
            fi
        fi
    fi
    
    # Verificar si hay composer.json para PHP
    if [ -f "composer.json" ]; then
        if command -v composer &>/dev/null; then
            log INFO "Instalando dependencias PHP..."
            composer install --no-dev --optimize-autoloader &>/dev/null
            log SUCCESS "Dependencias PHP instaladas"
        fi
    fi
    
    # Verificar sintaxis básica para archivos PHP
    if command -v php &>/dev/null; then
        local php_errors=0
        while IFS= read -r -d '' file; do
            if ! php -l "$file" &>/dev/null; then
                log ERROR "Error de sintaxis en: $file"
                ((php_errors++))
            fi
        done < <(find . -name "*.php" -print0)
        
        if [ $php_errors -gt 0 ]; then
            log ERROR "Se encontraron $php_errors archivos PHP con errores de sintaxis"
            return 1
        fi
        log SUCCESS "Sintaxis PHP verificada"
    fi
}

# Desplegar nueva versión
deploy_new_version() {
    log INFO "Desplegando nueva versión..."
    
    local source_dir="/tmp/deploy_$DEPLOYMENT_ID"
    
    # Crear directorio de aplicación si no existe
    mkdir -p "$APP_DIR"
    
    # Detener servicios temporalmente si es necesario
    if systemctl is-active "$SERVICE_NAME" &>/dev/null; then
        log INFO "Deteniendo servicio $SERVICE_NAME"
        systemctl stop "$SERVICE_NAME"
    fi
    
    # Copiar archivos nuevos
    if rsync -av --exclude='.git' "$source_dir/" "$APP_DIR/"; then
        log SUCCESS "Archivos copiados exitosamente"
    else
        log ERROR "Error copiando archivos"
        return 1
    fi
    
    # Establecer permisos
    chown -R www-data:www-data "$APP_DIR"
    find "$APP_DIR" -type f -exec chmod 644 {} \;
    find "$APP_DIR" -type d -exec chmod 755 {} \;
    
    # Hacer ejecutables los scripts necesarios
    if [ -d "$APP_DIR/bin" ]; then
        chmod +x "$APP_DIR/bin/"*
    fi
    
    log SUCCESS "Permisos establecidos"
    
    # Reiniciar servicios
    if systemctl is-enabled "$SERVICE_NAME" &>/dev/null; then
        log INFO "Iniciando servicio $SERVICE_NAME"
        systemctl start "$SERVICE_NAME"
    fi
    
    # Recargar nginx
    if systemctl is-active nginx &>/dev/null; then
        systemctl reload nginx
        log SUCCESS "Nginx recargado"
    fi
}

# Verificar despliegue
verify_deployment() {
    log INFO "Verificando despliegue..."
    
    # Verificar que los servicios estén activos
    local services_to_check=("$SERVICE_NAME" "nginx" "php-fpm")
    for service in "${services_to_check[@]}"; do
        if systemctl is-enabled "$service" &>/dev/null; then
            if systemctl is-active "$service" &>/dev/null; then
                log SUCCESS "Servicio $service está activo"
            else
                log ERROR "Servicio $service no está activo"
                return 1
            fi
        fi
    done
    
    # Health check HTTP
    local health_url="http://localhost/health"
    if command -v curl &>/dev/null; then
        log INFO "Ejecutando health check..."
        if curl -f -s "$health_url" &>/dev/null; then
            log SUCCESS "Health check HTTP exitoso"
        else
            log WARN "Health check HTTP falló"
        fi
    fi
    
    # Verificar archivos críticos
    local critical_files=("$APP_DIR/index.php" "$APP_DIR/config/app.php")
    for file in "${critical_files[@]}"; do
        if [ -f "$file" ]; then
            log SUCCESS "Archivo crítico presente: $file"
        else
            log WARN "Archivo crítico faltante: $file"
        fi
    done
    
    log SUCCESS "Verificación completada"
}

# Rollback en caso de error
rollback() {
    log ERROR "Iniciando rollback..."
    
    if [ "$ROLLBACK_AVAILABLE" = false ]; then
        log ERROR "No hay backup disponible para rollback"
        return 1
    fi
    
    local backup_path
    if [ -f "/tmp/last_backup_$APP_NAME" ]; then
        backup_path=$(cat "/tmp/last_backup_$APP_NAME")
    else
        log ERROR "No se encontró información de backup"
        return 1
    fi
    
    # Detener servicios
    if systemctl is-active "$SERVICE_NAME" &>/dev/null; then
        systemctl stop "$SERVICE_NAME"
    fi
    
    # Restaurar desde backup
    if [ -f "$backup_path/app_backup.tar.gz" ]; then
        rm -rf "$APP_DIR"
        mkdir -p "$APP_DIR"
        tar -xzf "$backup_path/app_backup.tar.gz" -C "$APP_DIR"
        chown -R www-data:www-data "$APP_DIR"
        log SUCCESS "Aplicación restaurada desde backup"
    fi
    
    # Restaurar base de datos si existe
    if [ -f "$backup_path/database_backup.sql" ] && command -v mysql &>/dev/null; then
        local db_name="${APP_NAME,,}"
        mysql "$db_name" < "$backup_path/database_backup.sql"
        log SUCCESS "Base de datos restaurada"
    fi
    
    # Reiniciar servicios
    if systemctl is-enabled "$SERVICE_NAME" &>/dev/null; then
        systemctl start "$SERVICE_NAME"
    fi
    
    if systemctl is-active nginx &>/dev/null; then
        systemctl reload nginx
    fi
    
    log SUCCESS "Rollback completado"
}

# Limpiar archivos temporales
cleanup() {
    log INFO "Limpiando archivos temporales..."
    
    rm -rf "/tmp/deploy_$DEPLOYMENT_ID" 2>/dev/null || true
    rm -f "/tmp/deploy_info_$APP_NAME" 2>/dev/null || true
    
    # Mantener solo los últimos 10 backups
    if [ -d "$BACKUP_DIR" ]; then
        find "$BACKUP_DIR" -maxdepth 1 -type d -name "deploy_*" | \
        sort -r | tail -n +11 | xargs -r rm -rf
    fi
    
    log SUCCESS "Limpieza completada"
}

# Enviar notificación
send_notification() {
    local status="$1"
    local message="$2"
    
    log INFO "Enviando notificación..."
    
    # Notificación por email (si está configurado)
    if command -v mail &>/dev/null && [ -n "${DEPLOY_EMAIL:-}" ]; then
        echo "$message" | mail -s "$APP_NAME: Despliegue $status" "$DEPLOY_EMAIL"
    fi
    
    # Notificación por Slack (si está configurado)
    if command -v curl &>/dev/null && [ -n "${SLACK_WEBHOOK:-}" ]; then
        local color="good"
        [ "$status" != "exitoso" ] && color="danger"
        
        local payload=$(cat << EOF
{
    "attachments": [
        {
            "color": "$color",
            "title": "$APP_NAME - Despliegue $status",
            "text": "$message",
            "ts": $(date +%s)
        }
    ]
}
EOF
        )
        
        curl -X POST -H 'Content-type: application/json' \
             --data "$payload" "$SLACK_WEBHOOK" &>/dev/null
    fi
}

# Función principal
main() {
    show_deployment_banner
    
    # Trap para manejar errores y ejecutar rollback automático
    trap 'handle_error' ERR
    
    # Ejecutar pipeline de despliegue
    check_prerequisites
    create_backup
    download_new_version
    run_tests
    deploy_new_version
    verify_deployment
    
    # Si llegamos aquí, el despliegue fue exitoso
    cleanup
    
    local success_message="Despliegue de $APP_NAME completado exitosamente (ID: $DEPLOYMENT_ID)"
    log SUCCESS "$success_message"
    send_notification "exitoso" "$success_message"
    
    echo ""
    echo -e "${GREEN}🎉 ¡Despliegue completado con éxito!${NC}"
    echo "ID: $DEPLOYMENT_ID"
    echo "Log: $DEPLOYMENT_LOG"
}

# Manejador de errores
handle_error() {
    local exit_code=$?
    log ERROR "Error detectado en el despliegue (código: $exit_code)"
    
    local error_message="Despliegue de $APP_NAME falló (ID: $DEPLOYMENT_ID). Ejecutando rollback automático."
    send_notification "fallido" "$error_message"
    
    # Ejecutar rollback automático
    rollback || log ERROR "Rollback también falló"
    
    cleanup
    exit $exit_code
}

# Verificar privilegios
if [ "$EUID" -ne 0 ]; then
    log ERROR "Este script debe ejecutarse con privilegios de root"
    exit 1
fi

# Ejecutar función principal
main "$@"
sudo ./deploy_basic.sh
DEPLOY_BRANCH=development sudo ./deploy_basic.sh

Estrategias de Despliegue Avanzadas

Blue-Green Deployment

Mantiene dos entornos idénticos (azul y verde) alternando entre ellos para despliegues sin downtime.

Rolling Deployment

Actualiza instancias de forma gradual, manteniendo siempre alguna versión disponible.

Canary Deployment

Despliega la nueva versión a un pequeño porcentaje de usuarios antes del rollout completo.

Blue-Green deployment - blue_green_deploy.sh
bash
#!/bin/bash

# Script de despliegue Blue-Green
# Permite despliegues sin downtime alternando entre dos entornos

set -euo pipefail

# Configuración
APP_NAME="webapp"
BLUE_DIR="/var/www/webapp-blue"
GREEN_DIR="/var/www/webapp-green"
CURRENT_LINK="/var/www/webapp-current"
NGINX_CONFIG="/etc/nginx/sites-available/webapp"
REPO_URL="https://github.com/user/webapp.git"
BRANCH="${DEPLOY_BRANCH:-main}"

# Colores
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'

# Función de logging
log() {
    local level="$1"
    shift
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] [$level] $*"
}

# Determinar entorno activo actual
get_current_environment() {
    if [ -L "$CURRENT_LINK" ]; then
        local target=$(readlink "$CURRENT_LINK")
        if [[ "$target" == *"blue"* ]]; then
            echo "blue"
        elif [[ "$target" == *"green"* ]]; then
            echo "green"
        else
            echo "unknown"
        fi
    else
        echo "none"
    fi
}

# Obtener directorio del entorno inactivo
get_inactive_environment() {
    local current=$(get_current_environment)
    case "$current" in
        "blue") echo "green" ;;
        "green") echo "blue" ;;
        *) echo "blue" ;;  # Por defecto usar blue si no hay entorno activo
    esac
}

# Obtener directorio del entorno
get_environment_dir() {
    local env="$1"
    case "$env" in
        "blue") echo "$BLUE_DIR" ;;
        "green") echo "$GREEN_DIR" ;;
        *) echo "" ;;
    esac
}

# Preparar entorno inactivo
prepare_inactive_environment() {
    local inactive_env=$(get_inactive_environment)
    local inactive_dir=$(get_environment_dir "$inactive_env")
    
    log INFO "Preparando entorno inactivo: $inactive_env"
    log INFO "Directorio: $inactive_dir"
    
    # Limpiar directorio si existe
    if [ -d "$inactive_dir" ]; then
        rm -rf "$inactive_dir"
    fi
    
    # Crear directorio
    mkdir -p "$inactive_dir"
    
    # Clonar código
    log INFO "Clonando código desde $REPO_URL (rama: $BRANCH)"
    if git clone -b "$BRANCH" --single-branch "$REPO_URL" "$inactive_dir"; then
        log INFO "✓ Código clonado exitosamente"
    else
        log ERROR "✗ Error clonando código"
        return 1
    fi
    
    # Cambiar al directorio y configurar
    cd "$inactive_dir"
    
    # Instalar dependencias si es necesario
    if [ -f "package.json" ] && command -v npm &>/dev/null; then
        log INFO "Instalando dependencias npm..."
        npm install --production &>/dev/null
        log INFO "✓ Dependencias npm instaladas"
    fi
    
    if [ -f "composer.json" ] && command -v composer &>/dev/null; then
        log INFO "Instalando dependencias PHP..."
        composer install --no-dev --optimize-autoloader &>/dev/null
        log INFO "✓ Dependencias PHP instaladas"
    fi
    
    # Configurar permisos
    chown -R www-data:www-data "$inactive_dir"
    find "$inactive_dir" -type f -exec chmod 644 {} \;
    find "$inactive_dir" -type d -exec chmod 755 {} \;
    
    # Ejecutar scripts de build si existen
    if [ -f "build.sh" ]; then
        log INFO "Ejecutando script de build..."
        bash build.sh
        log INFO "✓ Build completado"
    fi
    
    log INFO "✓ Entorno $inactive_env preparado"
    echo "$inactive_env"
}

# Ejecutar pruebas en el entorno
run_environment_tests() {
    local env="$1"
    local env_dir=$(get_environment_dir "$env")
    
    log INFO "Ejecutando pruebas en entorno $env"
    
    cd "$env_dir"
    
    # Ejecutar pruebas unitarias
    if [ -f "package.json" ] && command -v npm &>/dev/null; then
        if grep -q '"test"' package.json; then
            log INFO "Ejecutando pruebas npm..."
            if npm test &>/dev/null; then
                log INFO "✓ Pruebas npm pasaron"
            else
                log ERROR "✗ Pruebas npm fallaron"
                return 1
            fi
        fi
    fi
    
    # Verificar sintaxis PHP
    if command -v php &>/dev/null; then
        local php_errors=0
        while IFS= read -r -d '' file; do
            if ! php -l "$file" &>/dev/null; then
                log ERROR "Error de sintaxis en: $file"
                ((php_errors++))
            fi
        done < <(find . -name "*.php" -print0 2>/dev/null)
        
        if [ $php_errors -gt 0 ]; then
            log ERROR "✗ Errores de sintaxis PHP encontrados"
            return 1
        fi
        log INFO "✓ Sintaxis PHP verificada"
    fi
    
    log INFO "✓ Pruebas completadas exitosamente"
}

# Health check en el entorno
health_check() {
    local env="$1"
    local port="${2:-8080}"
    
    log INFO "Ejecutando health check en entorno $env (puerto $port)"
    
    # Configurar servidor temporal para pruebas
    local env_dir=$(get_environment_dir "$env")
    local pid_file="/tmp/test_server_${env}.pid"
    
    # Iniciar servidor PHP temporal para pruebas
    if [ -f "$env_dir/index.php" ]; then
        cd "$env_dir"
        php -S "localhost:$port" &>"$env_dir/test_server.log" &
        local server_pid=$!
        echo "$server_pid" > "$pid_file"
        
        # Esperar que el servidor inicie
        sleep 3
        
        # Ejecutar health check
        local health_url="http://localhost:$port/health"
        local fallback_url="http://localhost:$port/"
        
        if command -v curl &>/dev/null; then
            if curl -f -s "$health_url" &>/dev/null || curl -f -s "$fallback_url" &>/dev/null; then
                log INFO "✓ Health check exitoso"
                
                # Detener servidor temporal
                kill "$server_pid" 2>/dev/null || true
                rm -f "$pid_file"
                return 0
            else
                log ERROR "✗ Health check falló"
                
                # Detener servidor temporal
                kill "$server_pid" 2>/dev/null || true
                rm -f "$pid_file"
                return 1
            fi
        else
            log WARN "curl no disponible, omitiendo health check HTTP"
            kill "$server_pid" 2>/dev/null || true
            rm -f "$pid_file"
        fi
    fi
    
    return 0
}

# Cambiar entorno activo
switch_environment() {
    local new_env="$1"
    local new_dir=$(get_environment_dir "$new_env")
    local current_env=$(get_current_environment)
    
    log INFO "Cambiando de entorno $current_env a $new_env"
    
    # Crear backup del enlace actual
    if [ -L "$CURRENT_LINK" ]; then
        cp -L "$CURRENT_LINK" "${CURRENT_LINK}.backup" 2>/dev/null || true
    fi
    
    # Actualizar enlace simbólico
    rm -f "$CURRENT_LINK"
    ln -sf "$new_dir" "$CURRENT_LINK"
    
    # Verificar que el enlace se creó correctamente
    if [ -L "$CURRENT_LINK" ] && [ -d "$CURRENT_LINK" ]; then
        log INFO "✓ Enlace actualizado: $CURRENT_LINK -> $new_dir"
    else
        log ERROR "✗ Error actualizando enlace simbólico"
        return 1
    fi
    
    # Recargar nginx
    if systemctl is-active nginx &>/dev/null; then
        log INFO "Recargando nginx..."
        if systemctl reload nginx; then
            log INFO "✓ Nginx recargado"
        else
            log ERROR "✗ Error recargando nginx"
            return 1
        fi
    fi
    
    log INFO "✓ Cambio de entorno completado"
}

# Verificar despliegue final
verify_deployment() {
    local env="$1"
    
    log INFO "Verificando despliegue final..."
    
    # Verificar enlace simbólico
    if [ ! -L "$CURRENT_LINK" ]; then
        log ERROR "✗ Enlace simbólico no existe"
        return 1
    fi
    
    local current_target=$(readlink "$CURRENT_LINK")
    local expected_dir=$(get_environment_dir "$env")
    
    if [ "$current_target" != "$expected_dir" ]; then
        log ERROR "✗ Enlace simbólico apunta al directorio incorrecto"
        return 1
    fi
    
    # Verificar servicios
    if systemctl is-active nginx &>/dev/null; then
        log INFO "✓ Nginx está activo"
    else
        log ERROR "✗ Nginx no está activo"
        return 1
    fi
    
    # Health check final
    if command -v curl &>/dev/null; then
        local app_url="http://localhost"
        if curl -f -s "$app_url" &>/dev/null; then
            log INFO "✓ Aplicación responde correctamente"
        else
            log WARN "⚠ Aplicación no responde en $app_url"
        fi
    fi
    
    log INFO "✓ Verificación completada"
}

# Rollback a entorno anterior
rollback() {
    local current_env=$(get_current_environment)
    
    case "$current_env" in
        "blue") switch_environment "green" ;;
        "green") switch_environment "blue" ;;
        *)
            log ERROR "No se puede determinar el entorno para rollback"
            return 1
            ;;
    esac
    
    log INFO "✓ Rollback completado"
}

# Mostrar estado actual
show_status() {
    local current_env=$(get_current_environment)
    
    echo ""
    echo -e "${BLUE}=== ESTADO BLUE-GREEN DEPLOYMENT ===${NC}"
    echo "Entorno activo actual: $current_env"
    
    if [ -L "$CURRENT_LINK" ]; then
        local target=$(readlink "$CURRENT_LINK")
        echo "Enlace actual: $CURRENT_LINK -> $target"
    else
        echo "Sin enlace activo"
    fi
    
    # Mostrar estado de directorios
    echo ""
    echo "Estado de entornos:"
    for env in "blue" "green"; do
        local dir=$(get_environment_dir "$env")
        local status="No desplegado"
        local color="$RED"
        
        if [ -d "$dir" ]; then
            status="Disponible"
            color="$GREEN"
            
            if [ "$env" = "$current_env" ]; then
                status="Activo"
                color="$BLUE"
            fi
        fi
        
        echo -e "  $env: ${color}$status${NC} ($dir)"
    done
    echo ""
}

# Función principal
main() {
    case "${1:-deploy}" in
        deploy)
            echo -e "${BLUE}=== INICIANDO BLUE-GREEN DEPLOYMENT ===${NC}"
            
            show_status
            
            # Preparar entorno inactivo
            local inactive_env
            if ! inactive_env=$(prepare_inactive_environment); then
                log ERROR "Error preparando entorno inactivo"
                exit 1
            fi
            
            # Ejecutar pruebas
            if ! run_environment_tests "$inactive_env"; then
                log ERROR "Pruebas fallaron, cancelando despliegue"
                exit 1
            fi
            
            # Health check
            if ! health_check "$inactive_env" 8081; then
                log ERROR "Health check falló, cancelando despliegue"
                exit 1
            fi
            
            # Cambiar al nuevo entorno
            if ! switch_environment "$inactive_env"; then
                log ERROR "Error cambiando entorno"
                exit 1
            fi
            
            # Verificación final
            if ! verify_deployment "$inactive_env"; then
                log ERROR "Verificación falló, ejecutando rollback"
                rollback
                exit 1
            fi
            
            echo -e "${GREEN}✓ Despliegue Blue-Green completado exitosamente${NC}"
            show_status
            ;;
            
        rollback)
            echo -e "${YELLOW}=== EJECUTANDO ROLLBACK ===${NC}"
            rollback
            show_status
            ;;
            
        status)
            show_status
            ;;
            
        *)
            echo "Uso: $0 [deploy|rollback|status]"
            echo ""
            echo "Comandos:"
            echo "  deploy   - Ejecutar despliegue Blue-Green"
            echo "  rollback - Cambiar al entorno anterior"
            echo "  status   - Mostrar estado actual"
            exit 1
            ;;
    esac
}

# Verificar privilegios
if [ "$EUID" -ne 0 ]; then
    echo "Este script debe ejecutarse con privilegios de root"
    exit 1
fi

# Ejecutar función principal
main "$@"

Pipeline CI/CD con Bash

Pipeline CI/CD - ci_cd_pipeline.sh
bash
#!/bin/bash

# Pipeline CI/CD completo con Bash
# Integración con Git hooks, testing automático y despliegue

set -euo pipefail

# Configuración del pipeline
PIPELINE_ID="pipeline_$(date +%Y%m%d_%H%M%S)"
PROJECT_NAME="${PROJECT_NAME:-myapp}"
REPO_URL="${REPO_URL}"
BRANCH="${BRANCH:-main}"
BUILD_DIR="/tmp/builds/$PIPELINE_ID"
ARTIFACTS_DIR="/var/lib/ci-artifacts"
DEPLOY_ENV="${DEPLOY_ENV:-staging}"

# Configuración de entornos
declare -A ENVIRONMENTS=(
    ["staging"]="staging.myapp.com"
    ["production"]="myapp.com"
    ["testing"]="test.myapp.com"
)

# Configuración de notificaciones
SLACK_WEBHOOK="${SLACK_WEBHOOK:-}"
EMAIL_RECIPIENTS="${EMAIL_RECIPIENTS:-}"

# Colores y estilos
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
NC='\033[0m'

# Variables de estado del pipeline
PIPELINE_START_TIME=$(date +%s)
PIPELINE_STATUS="RUNNING"
FAILED_STAGE=""
STAGES_COMPLETED=()

# Crear directorios necesarios
mkdir -p "$BUILD_DIR" "$ARTIFACTS_DIR"

# Función de logging avanzado
log() {
    local level="$1"
    shift
    local message="$*"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    case "$level" in
        HEADER)
            echo -e "\n${BLUE}╔══════════════════════════════════════════════════════════════╗${NC}"
            echo -e "${BLUE}║$(printf "%-62s" "  $message")║${NC}"
            echo -e "${BLUE}╚══════════════════════════════════════════════════════════════╝${NC}\n"
            ;;
        STAGE)
            echo -e "\n${PURPLE}► $message${NC}"
            ;;
        INFO)
            echo -e "${CYAN}  ℹ $message${NC}"
            ;;
        SUCCESS)
            echo -e "${GREEN}  ✓ $message${NC}"
            ;;
        WARN)
            echo -e "${YELLOW}  ⚠ $message${NC}"
            ;;
        ERROR)
            echo -e "${RED}  ✗ $message${NC}"
            ;;
    esac
    
    # Log a archivo también
    echo "[$timestamp] [$level] $message" >> "$ARTIFACTS_DIR/$PIPELINE_ID.log"
}

# Mostrar progreso del pipeline
show_progress() {
    local completed="$1"
    local total="$2"
    local stage_name="$3"
    
    local percent=$((completed * 100 / total))
    local filled=$((percent * 40 / 100))
    
    printf "\n${WHITE}Pipeline Progress: [${NC}"
    for ((i=1; i<=40; i++)); do
        if [ $i -le $filled ]; then
            printf "${GREEN}█${NC}"
        else
            printf "░"
        fi
    done
    printf "${WHITE}] %d%% - %s${NC}\n\n" "$percent" "$stage_name"
}

# Enviar notificaciones
send_notification() {
    local status="$1"
    local message="$2"
    local detailed_message="$3"
    
    # Calcular duración del pipeline
    local end_time=$(date +%s)
    local duration=$((end_time - PIPELINE_START_TIME))
    local duration_formatted=$(printf "%02d:%02d" $((duration/60)) $((duration%60)))
    
    local full_message="$message
Proyecto: $PROJECT_NAME
Pipeline ID: $PIPELINE_ID
Duración: $duration_formatted
Entorno: $DEPLOY_ENV"
    
    # Notificación por Slack
    if [ -n "$SLACK_WEBHOOK" ] && command -v curl &>/dev/null; then
        local color="good"
        local icon=":white_check_mark:"
        
        case "$status" in
            "FAILED")
                color="danger"
                icon=":x:"
                ;;
            "WARNING")
                color="warning"
                icon=":warning:"
                ;;
        esac
        
        local payload=$(cat << EOF
{
    "attachments": [
        {
            "color": "$color",
            "title": "$icon $PROJECT_NAME - Pipeline $status",
            "text": "$full_message",
            "fields": [
                {
                    "title": "Branch",
                    "value": "$BRANCH",
                    "short": true
                },
                {
                    "title": "Environment",
                    "value": "$DEPLOY_ENV",
                    "short": true
                }
            ],
            "ts": $(date +%s)
        }
    ]
}
EOF
        )
        
        curl -X POST -H 'Content-type: application/json' \
             --data "$payload" "$SLACK_WEBHOOK" &>/dev/null || true
    fi
    
    # Notificación por email
    if [ -n "$EMAIL_RECIPIENTS" ] && command -v mail &>/dev/null; then
        {
            echo "$full_message"
            echo ""
            echo "Detalles:"
            echo "$detailed_message"
        } | mail -s "$PROJECT_NAME: Pipeline $status" "$EMAIL_RECIPIENTS"
    fi
}

# Stage 1: Checkout del código
stage_checkout() {
    log STAGE "Stage 1/7: Checkout del código"
    show_progress 1 7 "Checkout"
    
    cd "$BUILD_DIR"
    
    log INFO "Clonando repositorio: $REPO_URL"
    log INFO "Rama: $BRANCH"
    
    if git clone -b "$BRANCH" --single-branch "$REPO_URL" source; then
        log SUCCESS "Código descargado exitosamente"
        cd source
        
        # Obtener información del commit
        local commit_hash=$(git rev-parse HEAD)
        local commit_message=$(git log -1 --pretty=format:"%s")
        local commit_author=$(git log -1 --pretty=format:"%an")
        
        log INFO "Commit: $commit_hash"
        log INFO "Autor: $commit_author"
        log INFO "Mensaje: $commit_message"
        
        # Guardar información del pipeline
        cat > "$BUILD_DIR/pipeline_info.json" << EOF
{
    "pipeline_id": "$PIPELINE_ID",
    "project_name": "$PROJECT_NAME",
    "branch": "$BRANCH",
    "commit_hash": "$commit_hash",
    "commit_message": "$commit_message",
    "commit_author": "$commit_author",
    "timestamp": "$(date -Iseconds)",
    "environment": "$DEPLOY_ENV"
}
EOF
        
        STAGES_COMPLETED+=("checkout")
        return 0
    else
        log ERROR "Error descargando el código"
        return 1
    fi
}

# Stage 2: Análisis de código estático
stage_static_analysis() {
    log STAGE "Stage 2/7: Análisis de código estático"
    show_progress 2 7 "Análisis estático"
    
    cd "$BUILD_DIR/source"
    
    local analysis_errors=0
    
    # Análisis de sintaxis PHP
    if find . -name "*.php" | head -1 | grep -q php; then
        log INFO "Analizando sintaxis PHP..."
        while IFS= read -r -d '' file; do
            if ! php -l "$file" &>/dev/null; then
                log ERROR "Error de sintaxis en: $file"
                ((analysis_errors++))
            fi
        done < <(find . -name "*.php" -print0)
        
        if [ $analysis_errors -eq 0 ]; then
            log SUCCESS "Sintaxis PHP correcta"
        fi
    fi
    
    # Análisis con shellcheck para scripts Bash
    if command -v shellcheck &>/dev/null && find . -name "*.sh" | head -1 | grep -q sh; then
        log INFO "Analizando scripts Bash con shellcheck..."
        if find . -name "*.sh" -exec shellcheck {} \; &>/dev/null; then
            log SUCCESS "Scripts Bash verificados"
        else
            log WARN "Advertencias encontradas en scripts Bash"
        fi
    fi
    
    # Análisis de JavaScript/TypeScript con ESLint si está disponible
    if [ -f "package.json" ] && command -v npm &>/dev/null; then
        if npm list eslint &>/dev/null; then
            log INFO "Ejecutando ESLint..."
            if npm run lint &>/dev/null; then
                log SUCCESS "Linting JavaScript/TypeScript completado"
            else
                log WARN "Advertencias de linting encontradas"
            fi
        fi
    fi
    
    if [ $analysis_errors -gt 0 ]; then
        log ERROR "Análisis estático falló con $analysis_errors errores"
        return 1
    fi
    
    log SUCCESS "Análisis estático completado"
    STAGES_COMPLETED+=("static_analysis")
}

# Stage 3: Instalación de dependencias
stage_dependencies() {
    log STAGE "Stage 3/7: Instalación de dependencias"
    show_progress 3 7 "Dependencias"
    
    cd "$BUILD_DIR/source"
    
    # Dependencias npm/yarn
    if [ -f "package.json" ]; then
        if command -v yarn &>/dev/null && [ -f "yarn.lock" ]; then
            log INFO "Instalando dependencias con Yarn..."
            yarn install --frozen-lockfile &>/dev/null
            log SUCCESS "Dependencias Yarn instaladas"
        elif command -v npm &>/dev/null; then
            log INFO "Instalando dependencias con npm..."
            npm ci &>/dev/null
            log SUCCESS "Dependencias npm instaladas"
        fi
    fi
    
    # Dependencias Composer para PHP
    if [ -f "composer.json" ] && command -v composer &>/dev/null; then
        log INFO "Instalando dependencias PHP con Composer..."
        composer install --no-dev --optimize-autoloader --no-interaction &>/dev/null
        log SUCCESS "Dependencias PHP instaladas"
    fi
    
    # Dependencias Python
    if [ -f "requirements.txt" ] && command -v pip &>/dev/null; then
        log INFO "Instalando dependencias Python..."
        pip install -r requirements.txt &>/dev/null
        log SUCCESS "Dependencias Python instaladas"
    fi
    
    STAGES_COMPLETED+=("dependencies")
}

# Stage 4: Build de la aplicación
stage_build() {
    log STAGE "Stage 4/7: Build de la aplicación"
    show_progress 4 7 "Build"
    
    cd "$BUILD_DIR/source"
    
    # Build con npm/yarn
    if [ -f "package.json" ]; then
        local build_script=""
        if grep -q '"build"' package.json; then
            build_script="build"
        elif grep -q '"compile"' package.json; then
            build_script="compile"
        fi
        
        if [ -n "$build_script" ]; then
            log INFO "Ejecutando build script: $build_script"
            if command -v yarn &>/dev/null && [ -f "yarn.lock" ]; then
                yarn run "$build_script" &>/dev/null
            else
                npm run "$build_script" &>/dev/null
            fi
            log SUCCESS "Build completado"
        fi
    fi
    
    # Build personalizado
    if [ -f "build.sh" ]; then
        log INFO "Ejecutando script de build personalizado..."
        bash build.sh &>/dev/null
        log SUCCESS "Build personalizado completado"
    fi
    
    # Crear archivo de versión
    local version="${BRANCH}-$(git rev-parse --short HEAD)-$(date +%Y%m%d%H%M)"
    echo "$version" > VERSION
    log INFO "Versión: $version"
    
    STAGES_COMPLETED+=("build")
}

# Stage 5: Testing
stage_testing() {
    log STAGE "Stage 5/7: Ejecución de pruebas"
    show_progress 5 7 "Testing"
    
    cd "$BUILD_DIR/source"
    
    local test_failures=0
    
    # Pruebas unitarias con npm/yarn
    if [ -f "package.json" ] && grep -q '"test"' package.json; then
        log INFO "Ejecutando pruebas unitarias JavaScript..."
        if command -v yarn &>/dev/null && [ -f "yarn.lock" ]; then
            if yarn test &>/dev/null; then
                log SUCCESS "Pruebas JavaScript pasaron"
            else
                log ERROR "Pruebas JavaScript fallaron"
                ((test_failures++))
            fi
        else
            if npm test &>/dev/null; then
                log SUCCESS "Pruebas JavaScript pasaron"
            else
                log ERROR "Pruebas JavaScript fallaron"
                ((test_failures++))
            fi
        fi
    fi
    
    # Pruebas PHP con PHPUnit
    if [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
        if command -v phpunit &>/dev/null || [ -f "vendor/bin/phpunit" ]; then
            log INFO "Ejecutando pruebas PHPUnit..."
            local phpunit_cmd="phpunit"
            [ -f "vendor/bin/phpunit" ] && phpunit_cmd="vendor/bin/phpunit"
            
            if $phpunit_cmd &>/dev/null; then
                log SUCCESS "Pruebas PHP pasaron"
            else
                log ERROR "Pruebas PHP fallaron"
                ((test_failures++))
            fi
        fi
    fi
    
    # Pruebas Python
    if [ -f "pytest.ini" ] || [ -f "setup.cfg" ] || find . -name "test_*.py" | head -1 | grep -q test_; then
        if command -v pytest &>/dev/null; then
            log INFO "Ejecutando pruebas pytest..."
            if pytest &>/dev/null; then
                log SUCCESS "Pruebas Python pasaron"
            else
                log ERROR "Pruebas Python fallaron"
                ((test_failures++))
            fi
        fi
    fi
    
    # Pruebas de integración personalizadas
    if [ -f "test.sh" ]; then
        log INFO "Ejecutando pruebas personalizadas..."
        if bash test.sh &>/dev/null; then
            log SUCCESS "Pruebas personalizadas pasaron"
        else
            log ERROR "Pruebas personalizadas fallaron"
            ((test_failures++))
        fi
    fi
    
    if [ $test_failures -gt 0 ]; then
        log ERROR "Testing falló con $test_failures conjuntos de pruebas"
        return 1
    fi
    
    log SUCCESS "Todas las pruebas pasaron exitosamente"
    STAGES_COMPLETED+=("testing")
}

# Stage 6: Creación de artefactos
stage_artifacts() {
    log STAGE "Stage 6/7: Creación de artefactos"
    show_progress 6 7 "Artefactos"
    
    cd "$BUILD_DIR/source"
    
    local artifact_name="${PROJECT_NAME}-$(cat VERSION).tar.gz"
    local artifact_path="$ARTIFACTS_DIR/$artifact_name"
    
    log INFO "Creando artefacto: $artifact_name"
    
    # Excluir archivos innecesarios del artefacto
    local exclude_patterns=(
        ".git"
        "node_modules"
        ".npm"
        "tests"
        "test"
        "*.log"
        ".env*"
        "coverage"
    )
    
    local exclude_args=""
    for pattern in "${exclude_patterns[@]}"; do
        exclude_args+=" --exclude=$pattern"
    done
    
    # Crear artefacto comprimido
    if tar czf "$artifact_path" $exclude_args -C "$BUILD_DIR/source" .; then
        log SUCCESS "Artefacto creado: $artifact_path"
        
        # Calcular checksum
        local checksum=$(sha256sum "$artifact_path" | cut -d' ' -f1)
        echo "$checksum" > "$artifact_path.sha256"
        log INFO "Checksum SHA256: $checksum"
        
        # Copiar información del pipeline
        cp "$BUILD_DIR/pipeline_info.json" "$ARTIFACTS_DIR/${PIPELINE_ID}_info.json"
        
        # Crear metadatos del artefacto
        cat > "$ARTIFACTS_DIR/${artifact_name%.tar.gz}_metadata.json" << EOF
{
    "artifact_name": "$artifact_name",
    "project_name": "$PROJECT_NAME",
    "version": "$(cat VERSION)",
    "checksum_sha256": "$checksum",
    "size_bytes": $(stat -f%z "$artifact_path" 2>/dev/null || stat -c%s "$artifact_path"),
    "created_at": "$(date -Iseconds)",
    "pipeline_id": "$PIPELINE_ID"
}
EOF
        
        STAGES_COMPLETED+=("artifacts")
        return 0
    else
        log ERROR "Error creando artefacto"
        return 1
    fi
}

# Stage 7: Despliegue
stage_deployment() {
    log STAGE "Stage 7/7: Despliegue a $DEPLOY_ENV"
    show_progress 7 7 "Despliegue"
    
    local target_host="${ENVIRONMENTS[$DEPLOY_ENV]}"
    local artifact_name="${PROJECT_NAME}-$(cat $BUILD_DIR/source/VERSION).tar.gz"
    local artifact_path="$ARTIFACTS_DIR/$artifact_name"
    
    if [ -z "$target_host" ]; then
        log ERROR "Entorno de despliegue no configurado: $DEPLOY_ENV"
        return 1
    fi
    
    log INFO "Desplegando a: $target_host"
    log INFO "Artefacto: $artifact_name"
    
    # En un entorno real, aquí se haría el despliegue real
    # Por ejemplo: scp, rsync, kubectl, docker, etc.
    
    # Simular despliegue exitoso
    sleep 2
    
    # Verificar despliegue (health check)
    log INFO "Ejecutando verificación post-despliegue..."
    
    # Aquí iría un health check real
    # if curl -f "https://$target_host/health" &>/dev/null; then
    #     log SUCCESS "Health check exitoso"
    # else
    #     log ERROR "Health check falló"
    #     return 1
    # fi
    
    log SUCCESS "Despliegue completado exitosamente"
    log SUCCESS "Aplicación disponible en: https://$target_host"
    
    STAGES_COMPLETED+=("deployment")
}

# Manejar fallos del pipeline
handle_pipeline_failure() {
    local failed_stage="$1"
    PIPELINE_STATUS="FAILED"
    FAILED_STAGE="$failed_stage"
    
    log ERROR "Pipeline falló en stage: $failed_stage"
    
    # Generar reporte de fallo
    generate_failure_report
    
    # Enviar notificación de fallo
    send_notification "FAILED" \
        "Pipeline falló en stage: $failed_stage" \
        "Revisar logs en: $ARTIFACTS_DIR/$PIPELINE_ID.log"
    
    cleanup_pipeline
}

# Generar reporte de fallo
generate_failure_report() {
    local report_file="$ARTIFACTS_DIR/${PIPELINE_ID}_failure_report.json"
    
    cat > "$report_file" << EOF
{
    "pipeline_id": "$PIPELINE_ID",
    "project_name": "$PROJECT_NAME",
    "status": "$PIPELINE_STATUS",
    "failed_stage": "$FAILED_STAGE",
    "stages_completed": [$(IFS=,; echo "${STAGES_COMPLETED[*]/#/\"}" | sed 's/"/"/g' | sed 's/,/","/g')"],
    "duration_seconds": $(($(date +%s) - PIPELINE_START_TIME)),
    "timestamp": "$(date -Iseconds)",
    "log_file": "$ARTIFACTS_DIR/$PIPELINE_ID.log"
}
EOF
    
    log INFO "Reporte de fallo generado: $report_file"
}

# Limpiar recursos del pipeline
cleanup_pipeline() {
    log INFO "Limpiando recursos del pipeline..."
    
    # Eliminar directorio de build
    rm -rf "$BUILD_DIR" 2>/dev/null || true
    
    # Mantener solo los últimos 10 logs y artefactos
    find "$ARTIFACTS_DIR" -name "pipeline_*.log" -type f | sort -r | tail -n +11 | xargs -r rm -f
    find "$ARTIFACTS_DIR" -name "*_failure_report.json" -type f | sort -r | tail -n +11 | xargs -r rm -f
    
    log SUCCESS "Limpieza completada"
}

# Función principal del pipeline
run_pipeline() {
    log HEADER "PIPELINE CI/CD - $PROJECT_NAME"
    log INFO "Pipeline ID: $PIPELINE_ID"
    log INFO "Entorno objetivo: $DEPLOY_ENV"
    
    # Trap para manejar errores
    trap 'handle_pipeline_failure "${BASH_COMMAND%% *}"' ERR
    
    # Ejecutar stages del pipeline
    stage_checkout
    stage_static_analysis
    stage_dependencies
    stage_build
    stage_testing
    stage_artifacts
    stage_deployment
    
    # Pipeline completado exitosamente
    PIPELINE_STATUS="SUCCESS"
    
    local duration=$(($(date +%s) - PIPELINE_START_TIME))
    local duration_formatted=$(printf "%02d:%02d" $((duration/60)) $((duration%60)))
    
    log SUCCESS "Pipeline completado exitosamente en $duration_formatted"
    
    # Enviar notificación de éxito
    send_notification "SUCCESS" \
        "Pipeline completado exitosamente" \
        "Duración: $duration_formatted\nTodos los stages completados correctamente"
    
    cleanup_pipeline
}

# Función para mostrar ayuda
show_help() {
    cat << EOF
Pipeline CI/CD v1.0

USO:
    $0 [OPCIONES]

OPCIONES:
    --project NAME      Nombre del proyecto
    --repo URL          URL del repositorio Git
    --branch BRANCH     Rama a desplegar (default: main)
    --env ENV          Entorno de despliegue (staging/production/testing)
    --slack-webhook URL URL del webhook de Slack para notificaciones
    --email RECIPIENTS  Lista de emails separados por coma
    --help             Mostrar esta ayuda

VARIABLES DE ENTORNO:
    PROJECT_NAME       Nombre del proyecto
    REPO_URL          URL del repositorio
    BRANCH            Rama (default: main)
    DEPLOY_ENV        Entorno (default: staging)
    SLACK_WEBHOOK     Webhook de Slack
    EMAIL_RECIPIENTS  Emails para notificaciones

EJEMPLOS:
    $0 --project myapp --repo https://github.com/user/myapp.git --env staging
    
    PROJECT_NAME=myapp REPO_URL=https://github.com/user/myapp.git $0
EOF
}

# Procesar argumentos de línea de comandos
while [ $# -gt 0 ]; do
    case $1 in
        --project)
            PROJECT_NAME="$2"
            shift 2
            ;;
        --repo)
            REPO_URL="$2"
            shift 2
            ;;
        --branch)
            BRANCH="$2"
            shift 2
            ;;
        --env)
            DEPLOY_ENV="$2"
            shift 2
            ;;
        --slack-webhook)
            SLACK_WEBHOOK="$2"
            shift 2
            ;;
        --email)
            EMAIL_RECIPIENTS="$2"
            shift 2
            ;;
        --help|-h)
            show_help
            exit 0
            ;;
        *)
            log ERROR "Argumento desconocido: $1"
            show_help
            exit 1
            ;;
    esac
done

# Validar parámetros requeridos
if [ -z "${REPO_URL:-}" ]; then
    log ERROR "URL del repositorio es requerida"
    show_help
    exit 1
fi

if [ -z "${ENVIRONMENTS[$DEPLOY_ENV]:-}" ]; then
    log ERROR "Entorno de despliegue inválido: $DEPLOY_ENV"
    log INFO "Entornos disponibles: ${!ENVIRONMENTS[*]}"
    exit 1
fi

# Ejecutar pipeline
run_pipeline

Mejores Prácticas de Despliegue

  • Automatización completa: Minimiza la intervención manual en todos los procesos
  • Testing exhaustivo: Incluye pruebas unitarias, integración y end-to-end
  • Rollback automático: Implementa mecanismos de reversión rápida
  • Monitoreo continuo: Vigila métricas y logs post-despliegue
  • Despliegues incrementales: Usa estrategias que minimicen el riesgo
  • Documentación: Mantén documentados todos los procesos y configuraciones

Consideraciones Críticas

  • Secrets management: Nunca hardcodees credenciales en scripts
  • Validación de entornos: Verifica configuraciones antes del despliegue
  • Backup obligatorio: Siempre crea respaldos antes de cambios
  • Window de mantenimiento: Coordina despliegues con el negocio
  • Plan de contingencia: Ten procedimientos claros para emergencias

Ejercicios Prácticos

Ejercicio 1: Pipeline Multi-Entorno

Crea un pipeline que maneje múltiples entornos:

  • Despliegue automático a desarrollo en cada commit
  • Promoción manual a staging después de aprobación
  • Despliegue a producción con aprobación de múltiples personas
  • Rollback automático basado en métricas de salud

Ejercicio 2: Despliegue de Microservicios

Desarrolla un sistema para desplegar microservicios:

  • Orquestación de múltiples servicios interdependientes
  • Despliegues canary con análisis de tráfico
  • Health checks distribuidos
  • Rollback granular por servicio