#!/bin/bash # =============================================================================================================== # terraform-lock-cleanup.sh - Limpieza automática de locks bloqueados de Terraform # Descripción: # Verifica y elimina locks bloqueados de Terraform en DynamoDB antes de ejecutar terraform plan/apply. # Previene errores de "Error acquiring the state lock" en pipelines de CI/CD. # # Uso: # bash scripts/terraform-lock-cleanup.sh # Ejemplo: bash scripts/terraform-lock-cleanup.sh dev proyectosacc/terraform.tfstate # # Autor: Área de Tecnología y Desarrollo - CCsoft # =============================================================================================================== set -euo pipefail # Colores para output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Variables ENVIRONMENT="${1:-dev}" STATE_KEY="${2:-proyectosacc/terraform.tfstate}" LOCK_TABLE="${TERRAFORM_LOCK_TABLE:-terraform-locks}" STATE_BUCKET="${TERRAFORM_STATE_BUCKET:-ccsoft-terraform-state}" MAX_LOCK_AGE_MINUTES="${MAX_LOCK_AGE_MINUTES:-30}" AWS_REGION="${AWS_DEFAULT_REGION:-mx-central-1}" # Construir el LockID completo LOCK_ID="${STATE_BUCKET}/${STATE_KEY}-md5" echo "=== Terraform Lock Cleanup ===" echo "Environment: ${ENVIRONMENT}" echo "State Key: ${STATE_KEY}" echo "Lock Table: ${LOCK_TABLE}" echo "Lock ID: ${LOCK_ID}" echo "Max Lock Age: ${MAX_LOCK_AGE_MINUTES} minutes" echo "AWS Region: ${AWS_REGION}" echo "" # Verificar que AWS CLI está disponible if ! command -v aws &> /dev/null; then echo -e "${RED}ERROR: AWS CLI no está instalado${NC}" exit 1 fi # Verificar credenciales AWS echo "Verificando credenciales AWS..." if ! aws sts get-caller-identity &> /dev/null; then echo -e "${RED}ERROR: No se pueden validar credenciales AWS${NC}" exit 1 fi echo -e "${GREEN}✓ Credenciales AWS válidas${NC}" echo "" # Verificar si la tabla existe echo "Verificando tabla DynamoDB ${LOCK_TABLE}..." if ! aws dynamodb describe-table --table-name "${LOCK_TABLE}" --region "${AWS_REGION}" &> /dev/null; then echo -e "${YELLOW}⚠ Tabla ${LOCK_TABLE} no encontrada. No hay locks que limpiar.${NC}" exit 0 fi echo -e "${GREEN}✓ Tabla DynamoDB encontrada${NC}" echo "" # Buscar el lock echo "Buscando lock en DynamoDB..." LOCK_ITEM=$(aws dynamodb get-item \ --table-name "${LOCK_TABLE}" \ --key "{\"LockID\": {\"S\": \"${LOCK_ID}\"}}" \ --region "${AWS_REGION}" \ --output json 2>/dev/null || echo '{}') if [ -z "${LOCK_ITEM}" ] || [ "${LOCK_ITEM}" == '{}' ]; then echo -e "${GREEN}✓ No se encontró lock activo. Estado limpio.${NC}" exit 0 fi echo -e "${YELLOW}⚠ Lock encontrado en DynamoDB${NC}" echo "Detalles del lock:" echo "${LOCK_ITEM}" | python3 -m json.tool 2>/dev/null || echo "${LOCK_ITEM}" echo "" # Verificar si el lock tiene información de creación (Info field) LOCK_INFO=$(echo "${LOCK_ITEM}" | python3 -c " import json, sys try: item = json.load(sys.stdin) info = item.get('Item', {}).get('Info', {}).get('S', '{}') print(info) except: print('{}') " 2>/dev/null || echo '{}') if [ -n "${LOCK_INFO}" ] && [ "${LOCK_INFO}" != '{}' ]; then echo "Información del lock:" echo "${LOCK_INFO}" | python3 -m json.tool 2>/dev/null || echo "${LOCK_INFO}" # Extraer timestamp de creación CREATED=$(echo "${LOCK_INFO}" | python3 -c " import json, sys try: info = json.load(sys.stdin) created = info.get('Created', '') print(created) except: print('') " 2>/dev/null || echo '') if [ -n "${CREATED}" ]; then echo "" echo "Lock creado: ${CREATED}" # Calcular antigüedad del lock CREATED_EPOCH=$(date -d "${CREATED}" +%s 2>/dev/null || echo '0') CURRENT_EPOCH=$(date +%s) if [ "${CREATED_EPOCH}" != '0' ]; then AGE_MINUTES=$(( (CURRENT_EPOCH - CREATED_EPOCH) / 60 )) echo "Antigüedad del lock: ${AGE_MINUTES} minutos" if [ ${AGE_MINUTES} -gt ${MAX_LOCK_AGE_MINUTES} ]; then echo -e "${YELLOW}⚠ Lock tiene más de ${MAX_LOCK_AGE_MINUTES} minutos. Considerado bloqueado.${NC}" else echo -e "${YELLOW}⚠ Lock tiene menos de ${MAX_LOCK_AGE_MINUTES} minutos. Podría ser una operación en curso.${NC}" echo -e "${YELLOW}⚠ Esperando 30 segundos antes de verificar nuevamente...${NC}" sleep 30 # Verificar nuevamente LOCK_ITEM_RETRY=$(aws dynamodb get-item \ --table-name "${LOCK_TABLE}" \ --key "{\"LockID\": {\"S\": \"${LOCK_ID}\"}}" \ --region "${AWS_REGION}" \ --output json 2>/dev/null || echo '{}') if [ -z "${LOCK_ITEM_RETRY}" ] || [ "${LOCK_ITEM_RETRY}" == '{}' ]; then echo -e "${GREEN}✓ Lock liberado durante la espera. Estado limpio.${NC}" exit 0 fi echo -e "${YELLOW}⚠ Lock todavía presente después de esperar. Procediendo con limpieza...${NC}" fi fi fi fi echo "" echo -e "${YELLOW}⚠ Eliminando lock bloqueado de DynamoDB...${NC}" # Eliminar el lock if aws dynamodb delete-item \ --table-name "${LOCK_TABLE}" \ --key "{\"LockID\": {\"S\": \"${LOCK_ID}\"}}" \ --region "${AWS_REGION}" \ --condition-expression "attribute_exists(LockID)" \ 2>/dev/null; then echo -e "${GREEN}✓ Lock eliminado exitosamente${NC}" else echo -e "${YELLOW}⚠ No se pudo eliminar el lock (puede que ya no exista)${NC}" fi echo "" echo "=== Limpieza de locks completada ==="