f32b58fc46
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
402 lines
18 KiB
YAML
402 lines
18 KiB
YAML
# ===============================================================================================================
|
|
# bitbucket-pipelines.yml - Pipeline CI/CD para proyectosacc
|
|
# Descripción:
|
|
# Pipeline de 7 pasos estándar de CCsoft para desplegar infraestructura (Terraform),
|
|
# frontend React (S3+CloudFront) y API backend (EC2) de SACC.
|
|
#
|
|
# Autor: Área de Tecnología y Desarrollo - CCsoft
|
|
# ===============================================================================================================
|
|
|
|
image: atlassian/default-image:5
|
|
|
|
options:
|
|
max-time: 120
|
|
oidc:
|
|
audiences:
|
|
- sts.amazonaws.com
|
|
|
|
definitions:
|
|
steps:
|
|
- step: &install-aws-cli
|
|
name: Install AWS CLI
|
|
script:
|
|
- apt-get update -y && apt-get install -y curl unzip
|
|
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
- unzip -q awscliv2.zip
|
|
- ./aws/install
|
|
- aws --version
|
|
|
|
- step: ¬ify-start
|
|
name: Notify Start
|
|
script:
|
|
- export TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN}"
|
|
- export TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID}"
|
|
- bash scripts/telegram-pipeline-notify.sh start
|
|
|
|
- step: ¬ify-fail
|
|
name: Notify Failure
|
|
script:
|
|
- export TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN}"
|
|
- export TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID}"
|
|
- bash scripts/telegram-pipeline-notify.sh failure "Paso: ${BITBUCKET_STEP_KEY}"
|
|
|
|
pipelines:
|
|
default:
|
|
- step:
|
|
name: 04_build
|
|
script:
|
|
- set -euo pipefail
|
|
- echo "=== Build de proyectosacc (sin deploy) ==="
|
|
- |
|
|
if [ -f package.json ]; then
|
|
npm ci
|
|
npm run build
|
|
else
|
|
echo "INFO: No se encontró package.json. Saltando build npm."
|
|
fi
|
|
- |
|
|
if [ -f gradlew ] || [ -f build.gradle ]; then
|
|
./gradlew clean bootJar
|
|
else
|
|
echo "INFO: No se encontró gradlew ni build.gradle. Saltando build Gradle."
|
|
fi
|
|
- echo "Build condicional completado."
|
|
|
|
branches:
|
|
developer:
|
|
- step:
|
|
name: 01_image-setup
|
|
script:
|
|
- set -euo pipefail
|
|
- apt-get update -y && apt-get install -y openssh-client openjdk-21-jdk wget unzip curl
|
|
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
- unzip -q awscliv2.zip
|
|
- ./aws/install
|
|
- aws --version
|
|
- mkdir -p ~/.ssh
|
|
- echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
|
|
- chmod 600 ~/.ssh/sacc4_key
|
|
- ssh-keyscan -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" "${DEV_SERVER_IP_PROYECTOSACC}" >> ~/.ssh/known_hosts 2>/dev/null || true
|
|
- export TELEGRAM_BOT_TOKEN="${DEV_TELEGRAM_BOT_TOKEN}"
|
|
- export TELEGRAM_CHAT_ID="${DEV_TELEGRAM_CHAT_ID}"
|
|
- bash scripts/telegram-pipeline-notify.sh start
|
|
|
|
- step:
|
|
name: 02_pre_terraform_check
|
|
oidc: true
|
|
script:
|
|
- set -euo pipefail
|
|
- apt-get update -y && apt-get install -y curl unzip
|
|
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
- unzip -q awscliv2.zip
|
|
- ./aws/install
|
|
- aws --version
|
|
- source scripts/aws-oidc-setup.sh dev
|
|
- bash scripts/terraform-lock-cleanup.sh dev proyectosacc/terraform.tfstate
|
|
|
|
- step:
|
|
name: 03_terraform
|
|
oidc: true
|
|
script:
|
|
- set -euo pipefail
|
|
- apt-get update -y && apt-get install -y curl unzip
|
|
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
- unzip -q awscliv2.zip
|
|
- ./aws/install
|
|
- aws --version
|
|
- source scripts/aws-oidc-setup.sh dev
|
|
- cd terraform
|
|
- wget -q "https://releases.hashicorp.com/terraform/1.11.4/terraform_1.11.4_linux_amd64.zip"
|
|
- unzip -q terraform_1.11.4_linux_amd64.zip
|
|
- mv terraform /usr/local/bin/terraform
|
|
- terraform version
|
|
- export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-mx-central-1}"
|
|
- terraform init -backend-config=backend.dev.hcl
|
|
- terraform plan -lock-timeout=5m -var-file=environments/dev.tfvars -var="db_password=${DEV_DB_PASSWORD}" -out=dev.tfplan
|
|
- terraform apply -lock-timeout=5m -auto-approve dev.tfplan
|
|
- terraform output -json > terraform-outputs.json
|
|
- cat terraform-outputs.json
|
|
artifacts:
|
|
- terraform/terraform-outputs.json
|
|
|
|
- step:
|
|
name: 04_build
|
|
script:
|
|
- set -euo pipefail
|
|
- |
|
|
if [ -f package.json ]; then
|
|
npm ci
|
|
npm run build
|
|
else
|
|
echo "INFO: No se encontró package.json. Saltando build npm."
|
|
fi
|
|
- |
|
|
if [ -f gradlew ] || [ -f build.gradle ]; then
|
|
./gradlew clean bootJar
|
|
else
|
|
echo "INFO: No se encontró gradlew ni build.gradle. Saltando build Gradle."
|
|
fi
|
|
- echo "Build condicional completado."
|
|
artifacts:
|
|
- build/**
|
|
- build/libs/*.jar
|
|
|
|
- step:
|
|
name: 05_publish
|
|
oidc: true
|
|
script:
|
|
- set -euo pipefail
|
|
- apt-get update -y && apt-get install -y curl unzip
|
|
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
- unzip -q awscliv2.zip
|
|
- ./aws/install
|
|
- aws --version
|
|
- source scripts/aws-oidc-setup.sh dev
|
|
- |
|
|
if [ -d build/ ] && [ "$(ls -A build/ 2>/dev/null)" ]; then
|
|
aws s3 sync build/ "s3://${DEV_S3_FRONTEND_BUCKET}/" --delete
|
|
else
|
|
echo "INFO: No se encontró directorio build/ con contenido. Saltando sync a S3."
|
|
fi
|
|
- |
|
|
if ls build/libs/*.jar >/dev/null 2>&1; then
|
|
aws s3 cp build/libs/*.jar "s3://${DEV_S3_ARTIFACTS_BUCKET}/develop/proyectosacc-app.jar"
|
|
else
|
|
echo "INFO: No se encontró JAR en build/libs/. Saltando copia a S3."
|
|
fi
|
|
- echo "Publish condicional completado."
|
|
|
|
- step:
|
|
name: 06_install
|
|
script:
|
|
- set -euo pipefail
|
|
- apt-get update -y && apt-get install -y curl unzip
|
|
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
- unzip -q awscliv2.zip
|
|
- ./aws/install
|
|
- aws --version
|
|
- |
|
|
JAR_LOCAL_PATTERN="build/libs/*.jar"
|
|
JAR_S3_URI="s3://${DEV_S3_ARTIFACTS_BUCKET}/develop/proyectosacc-app.jar"
|
|
HAS_LOCAL_JAR=false
|
|
if ls ${JAR_LOCAL_PATTERN} >/dev/null 2>&1; then
|
|
HAS_LOCAL_JAR=true
|
|
fi
|
|
if [ "${HAS_LOCAL_JAR}" = "true" ]; then
|
|
echo "INFO: Artefacto JAR encontrado localmente. Procediendo con instalación en servidor."
|
|
echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
|
|
chmod 600 ~/.ssh/sacc4_key
|
|
ssh -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" \
|
|
-i ~/.ssh/sacc4_key \
|
|
-o StrictHostKeyChecking=no \
|
|
"${DEV_SERVER_USER_PROYECTOSACC:-thoth}@${DEV_SERVER_IP_PROYECTOSACC}" \
|
|
"bash -c 'mkdir -p /home/thoth/deploy/artifacts/current && aws s3 cp ${JAR_S3_URI} /home/thoth/deploy/artifacts/current/proyectosacc-app.jar && chown osiris:osiris /home/thoth/deploy/artifacts/current/proyectosacc-app.jar'"
|
|
else
|
|
echo "INFO: No se encontró artefacto JAR localmente. Saltando instalación."
|
|
fi
|
|
- echo "Install condicional completado."
|
|
|
|
- step:
|
|
name: 07_deploy
|
|
oidc: true
|
|
script:
|
|
- set -euo pipefail
|
|
- apt-get update -y && apt-get install -y curl unzip
|
|
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
- unzip -q awscliv2.zip
|
|
- ./aws/install
|
|
- aws --version
|
|
- source scripts/aws-oidc-setup.sh dev
|
|
- echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
|
|
- chmod 600 ~/.ssh/sacc4_key
|
|
- |
|
|
ssh -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" \
|
|
-i ~/.ssh/sacc4_key \
|
|
-o StrictHostKeyChecking=no \
|
|
"${DEV_SERVER_USER_PROYECTOSACC:-thoth}@${DEV_SERVER_IP_PROYECTOSACC}" \
|
|
"bash -c 'if [ -f /home/thoth/deploy/setup/deploy.sh ]; then bash /home/thoth/deploy/setup/deploy.sh; else echo \"INFO: No se encontró script de deploy. Saltando deploy backend.\"; fi'"
|
|
- |
|
|
if [ -f terraform/terraform-outputs.json ]; then
|
|
export CLOUDFRONT_DISTRIBUTION_ID=$(python3 -c "import json; print(json.load(open('terraform/terraform-outputs.json'))['cloudfront_distribution_id']['value'])")
|
|
aws cloudfront create-invalidation --distribution-id "${CLOUDFRONT_DISTRIBUTION_ID}" --paths "/*"
|
|
else
|
|
echo "INFO: No se encontró terraform-outputs.json. Saltando invalidación de CloudFront."
|
|
fi
|
|
- export TELEGRAM_BOT_TOKEN="${DEV_TELEGRAM_BOT_TOKEN}"
|
|
- export TELEGRAM_CHAT_ID="${DEV_TELEGRAM_CHAT_ID}"
|
|
- bash scripts/telegram-pipeline-notify.sh success "Deploy condicional completado"
|
|
|
|
master:
|
|
- step:
|
|
name: 01_image-setup
|
|
script:
|
|
- set -euo pipefail
|
|
- apt-get update -y && apt-get install -y openssh-client openjdk-21-jdk wget unzip curl
|
|
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
- unzip -q awscliv2.zip
|
|
- ./aws/install
|
|
- aws --version
|
|
- mkdir -p ~/.ssh
|
|
- echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
|
|
- chmod 600 ~/.ssh/sacc4_key
|
|
- ssh-keyscan -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" "${PROD_SERVER_IP_PROYECTOSACC}" >> ~/.ssh/known_hosts 2>/dev/null || true
|
|
- export TELEGRAM_BOT_TOKEN="${PROD_TELEGRAM_BOT_TOKEN}"
|
|
- export TELEGRAM_CHAT_ID="${PROD_TELEGRAM_CHAT_ID}"
|
|
- bash scripts/telegram-pipeline-notify.sh start
|
|
|
|
- step:
|
|
name: 02_pre_terraform_check
|
|
oidc: true
|
|
script:
|
|
- set -euo pipefail
|
|
- apt-get update -y && apt-get install -y curl unzip
|
|
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
- unzip -q awscliv2.zip
|
|
- ./aws/install
|
|
- aws --version
|
|
- source scripts/aws-oidc-setup.sh prod
|
|
- bash scripts/terraform-lock-cleanup.sh prod proyectosacc/terraform.tfstate
|
|
|
|
- step:
|
|
name: 03_terraform
|
|
oidc: true
|
|
script:
|
|
- set -euo pipefail
|
|
- apt-get update -y && apt-get install -y curl unzip
|
|
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
- unzip -q awscliv2.zip
|
|
- ./aws/install
|
|
- aws --version
|
|
- source scripts/aws-oidc-setup.sh prod
|
|
- cd terraform
|
|
- wget -q "https://releases.hashicorp.com/terraform/1.11.4/terraform_1.11.4_linux_amd64.zip"
|
|
- unzip -q terraform_1.11.4_linux_amd64.zip
|
|
- mv terraform /usr/local/bin/terraform
|
|
- terraform version
|
|
- export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-mx-central-1}"
|
|
- terraform init -backend-config=backend.prod.hcl
|
|
- terraform plan -lock-timeout=5m -var-file=environments/prod.tfvars -var="db_password=${PROD_DB_PASSWORD}" -out=prod.tfplan
|
|
- terraform apply -lock-timeout=5m -auto-approve prod.tfplan
|
|
- terraform output -json > terraform-outputs.json
|
|
- cat terraform-outputs.json
|
|
artifacts:
|
|
- terraform/terraform-outputs.json
|
|
|
|
- step:
|
|
name: 04_build
|
|
script:
|
|
- set -euo pipefail
|
|
- |
|
|
if [ -f package.json ]; then
|
|
npm ci
|
|
npm run build
|
|
else
|
|
echo "INFO: No se encontró package.json. Saltando build npm."
|
|
fi
|
|
- |
|
|
if [ -f gradlew ] || [ -f build.gradle ]; then
|
|
./gradlew clean bootJar
|
|
else
|
|
echo "INFO: No se encontró gradlew ni build.gradle. Saltando build Gradle."
|
|
fi
|
|
- echo "Build condicional completado."
|
|
artifacts:
|
|
- build/**
|
|
- build/libs/*.jar
|
|
|
|
- step:
|
|
name: 05_publish
|
|
oidc: true
|
|
script:
|
|
- set -euo pipefail
|
|
- apt-get update -y && apt-get install -y curl unzip
|
|
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
- unzip -q awscliv2.zip
|
|
- ./aws/install
|
|
- aws --version
|
|
- source scripts/aws-oidc-setup.sh prod
|
|
- |
|
|
if [ -d build/ ] && [ "$(ls -A build/ 2>/dev/null)" ]; then
|
|
aws s3 sync build/ "s3://${PROD_S3_FRONTEND_BUCKET}/" --delete
|
|
else
|
|
echo "INFO: No se encontró directorio build/ con contenido. Saltando sync a S3."
|
|
fi
|
|
- |
|
|
if ls build/libs/*.jar >/dev/null 2>&1; then
|
|
aws s3 cp build/libs/*.jar "s3://${PROD_S3_ARTIFACTS_BUCKET}/main/proyectosacc-app.jar"
|
|
else
|
|
echo "INFO: No se encontró JAR en build/libs/. Saltando copia a S3."
|
|
fi
|
|
- echo "Publish condicional completado."
|
|
|
|
- step:
|
|
name: 06_install
|
|
script:
|
|
- set -euo pipefail
|
|
- apt-get update -y && apt-get install -y curl unzip
|
|
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
- unzip -q awscliv2.zip
|
|
- ./aws/install
|
|
- aws --version
|
|
- |
|
|
JAR_LOCAL_PATTERN="build/libs/*.jar"
|
|
JAR_S3_URI="s3://${PROD_S3_ARTIFACTS_BUCKET}/main/proyectosacc-app.jar"
|
|
HAS_LOCAL_JAR=false
|
|
if ls ${JAR_LOCAL_PATTERN} >/dev/null 2>&1; then
|
|
HAS_LOCAL_JAR=true
|
|
fi
|
|
if [ "${HAS_LOCAL_JAR}" = "true" ]; then
|
|
echo "INFO: Artefacto JAR encontrado localmente. Procediendo con instalación en servidor."
|
|
echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
|
|
chmod 600 ~/.ssh/sacc4_key
|
|
ssh -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" \
|
|
-i ~/.ssh/sacc4_key \
|
|
-o StrictHostKeyChecking=no \
|
|
"${PROD_SERVER_USER_PROYECTOSACC:-thoth}@${PROD_SERVER_IP_PROYECTOSACC}" \
|
|
"bash -c 'mkdir -p /home/thoth/deploy/artifacts/current && aws s3 cp ${JAR_S3_URI} /home/thoth/deploy/artifacts/current/proyectosacc-app.jar && chown osiris:osiris /home/thoth/deploy/artifacts/current/proyectosacc-app.jar'"
|
|
else
|
|
echo "INFO: No se encontró artefacto JAR localmente. Saltando instalación."
|
|
fi
|
|
- echo "Install condicional completado."
|
|
|
|
- step:
|
|
name: 06b_notify_approval
|
|
script:
|
|
- set -euo pipefail
|
|
- export TELEGRAM_BOT_TOKEN="${PROD_TELEGRAM_BOT_TOKEN}"
|
|
- export TELEGRAM_CHAT_ID="${PROD_TELEGRAM_CHAT_ID}"
|
|
- |
|
|
bash scripts/telegram-pipeline-notify.sh start "⏸️ Pipeline pausado esperando aprobación manual para deploy a PRODUCCIÓN. Ve a Bitbucket > Pipelines > proyectosacc > master para aprobar o rechazar."
|
|
|
|
- step:
|
|
name: 07_deploy
|
|
oidc: true
|
|
deployment: production
|
|
trigger: manual
|
|
script:
|
|
- set -euo pipefail
|
|
- apt-get update -y && apt-get install -y curl unzip
|
|
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
|
- unzip -q awscliv2.zip
|
|
- ./aws/install
|
|
- aws --version
|
|
- source scripts/aws-oidc-setup.sh prod
|
|
- echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
|
|
- chmod 600 ~/.ssh/sacc4_key
|
|
- |
|
|
ssh -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" \
|
|
-i ~/.ssh/sacc4_key \
|
|
-o StrictHostKeyChecking=no \
|
|
"${PROD_SERVER_USER_PROYECTOSACC:-thoth}@${PROD_SERVER_IP_PROYECTOSACC}" \
|
|
"bash -c 'if [ -f /home/thoth/deploy/setup/deploy.sh ]; then bash /home/thoth/deploy/setup/deploy.sh; else echo \"INFO: No se encontró script de deploy. Saltando deploy backend.\"; fi'"
|
|
- |
|
|
if [ -f terraform/terraform-outputs.json ]; then
|
|
export CLOUDFRONT_DISTRIBUTION_ID=$(python3 -c "import json; print(json.load(open('terraform/terraform-outputs.json'))['cloudfront_distribution_id']['value'])")
|
|
aws cloudfront create-invalidation --distribution-id "${CLOUDFRONT_DISTRIBUTION_ID}" --paths "/*"
|
|
else
|
|
echo "INFO: No se encontró terraform-outputs.json. Saltando invalidación de CloudFront."
|
|
fi
|
|
- export TELEGRAM_BOT_TOKEN="${PROD_TELEGRAM_BOT_TOKEN}"
|
|
- export TELEGRAM_CHAT_ID="${PROD_TELEGRAM_CHAT_ID}"
|
|
- bash scripts/telegram-pipeline-notify.sh success "CloudFront invalidado | Deploy a PROD aprobado y completado"
|