Files
proyectosacc-mirror/scripts/terraform-lock-cleanup.sh
Evert Romero f32b58fc46 fix(pipeline): implementar manejo robusto de state locks de Terraform
Problema:
- El pipeline fallaba con 'Error acquiring the state lock' cuando un proceso
  anterior de Terraform no liberaba el lock correctamente
- Los locks bloqueados requerían intervención manual

Solución implementada:
1. Nuevo script scripts/terraform-lock-cleanup.sh:
   - Verifica locks existentes en DynamoDB antes de ejecutar Terraform
   - Calcula antigüedad del lock (default: 30 minutos)
   - Elimina locks bloqueados automáticamente
   - Espera si el lock es reciente (operación en curso legítima)

2. Nuevo step 02_pre_terraform_check:
   - Ejecuta antes del step 03_terraform
   - Instala AWS CLI y configura credenciales
   - Limpia locks bloqueados antes de iniciar Terraform

3. Agregado -lock-timeout=5m a comandos Terraform:
   - terraform plan -lock-timeout=5m
   - terraform apply -lock-timeout=5m
   - Permite esperar si hay una operación legítima en curso

4. Aplicado a ambas ramas:
   - developer: cleanup para entorno dev
   - master: cleanup para entorno prod

Beneficios:
- Pipeline más robusto y autónomo
- Menos intervención manual para locks bloqueados
- Mejor manejo de concurrencia entre pipelines
- Previene corrupción de estado por locks huérfanos

Refs: Build #64 falló por state lock en DynamoDB
2026-04-17 11:11:06 -06:00

165 lines
5.7 KiB
Bash

#!/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 <environment> <state-key>
# 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 ==="