erp-suite/jenkins/Jenkinsfile

450 lines
15 KiB
Groovy

// =============================================================================
// ERP-SUITE - Jenkins Multi-Vertical Pipeline
// =============================================================================
// Gestiona el despliegue de erp-core y todas las verticales
// Servidor: 72.60.226.4
// =============================================================================
pipeline {
agent any
parameters {
choice(
name: 'VERTICAL',
choices: ['erp-core', 'construccion', 'vidrio-templado', 'mecanicas-diesel', 'retail', 'clinicas', 'pos-micro', 'ALL'],
description: 'Vertical a desplegar (ALL despliega todas las activas)'
)
choice(
name: 'ENVIRONMENT',
choices: ['staging', 'production'],
description: 'Ambiente de despliegue'
)
booleanParam(
name: 'RUN_MIGRATIONS',
defaultValue: false,
description: 'Ejecutar migraciones de BD'
)
booleanParam(
name: 'SKIP_TESTS',
defaultValue: false,
description: 'Saltar tests (solo para hotfixes)'
)
}
environment {
PROJECT_NAME = 'erp-suite'
DOCKER_REGISTRY = '72.60.226.4:5000'
DEPLOY_SERVER = '72.60.226.4'
DEPLOY_USER = 'deploy'
DEPLOY_PATH = '/opt/apps/erp-suite'
VERSION = "${env.BUILD_NUMBER}"
GIT_COMMIT_SHORT = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
// Verticales activas (con código desarrollado)
ACTIVE_VERTICALS = 'erp-core,construccion,mecanicas-diesel'
}
options {
timeout(time: 60, unit: 'MINUTES')
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '15'))
timestamps()
}
stages {
// =====================================================================
// PREPARACIÓN
// =====================================================================
stage('Checkout') {
steps {
checkout scm
script {
env.GIT_BRANCH = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim()
currentBuild.displayName = "#${BUILD_NUMBER} - ${params.VERTICAL} - ${GIT_COMMIT_SHORT}"
}
}
}
stage('Determine Verticals') {
steps {
script {
if (params.VERTICAL == 'ALL') {
env.VERTICALS_TO_BUILD = env.ACTIVE_VERTICALS
} else {
env.VERTICALS_TO_BUILD = params.VERTICAL
}
echo "Verticales a construir: ${env.VERTICALS_TO_BUILD}"
}
}
}
// =====================================================================
// BUILD & TEST POR VERTICAL
// =====================================================================
stage('Build Verticals') {
steps {
script {
def verticals = env.VERTICALS_TO_BUILD.split(',')
def parallelStages = [:]
verticals.each { vertical ->
parallelStages["Build ${vertical}"] = {
buildVertical(vertical)
}
}
parallel parallelStages
}
}
}
stage('Run Tests') {
when {
expression { return !params.SKIP_TESTS }
}
steps {
script {
def verticals = env.VERTICALS_TO_BUILD.split(',')
def parallelTests = [:]
verticals.each { vertical ->
parallelTests["Test ${vertical}"] = {
testVertical(vertical)
}
}
parallel parallelTests
}
}
}
// =====================================================================
// DOCKER BUILD & PUSH
// =====================================================================
stage('Docker Build & Push') {
when {
anyOf {
branch 'main'
branch 'develop'
}
}
steps {
script {
def verticals = env.VERTICALS_TO_BUILD.split(',')
verticals.each { vertical ->
def config = getVerticalConfig(vertical)
// Build Backend
if (fileExists("${config.path}/backend/Dockerfile")) {
sh """
docker build -t ${DOCKER_REGISTRY}/erp-${vertical}-backend:${VERSION} \
-t ${DOCKER_REGISTRY}/erp-${vertical}-backend:latest \
${config.path}/backend/
docker push ${DOCKER_REGISTRY}/erp-${vertical}-backend:${VERSION}
docker push ${DOCKER_REGISTRY}/erp-${vertical}-backend:latest
"""
}
// Build Frontend
def frontendPath = config.frontendPath ?: "${config.path}/frontend"
if (fileExists("${frontendPath}/Dockerfile")) {
sh """
docker build -t ${DOCKER_REGISTRY}/erp-${vertical}-frontend:${VERSION} \
-t ${DOCKER_REGISTRY}/erp-${vertical}-frontend:latest \
${frontendPath}/
docker push ${DOCKER_REGISTRY}/erp-${vertical}-frontend:${VERSION}
docker push ${DOCKER_REGISTRY}/erp-${vertical}-frontend:latest
"""
}
echo "✅ Docker images pushed for ${vertical}"
}
}
}
}
// =====================================================================
// DATABASE MIGRATIONS
// =====================================================================
stage('Run Migrations') {
when {
expression { return params.RUN_MIGRATIONS }
}
steps {
script {
// Siempre ejecutar migraciones de erp-core primero
if (env.VERTICALS_TO_BUILD.contains('erp-core') || params.VERTICAL == 'ALL') {
echo "Ejecutando migraciones de erp-core..."
runMigrations('erp-core')
}
// Luego migraciones de verticales
def verticals = env.VERTICALS_TO_BUILD.split(',')
verticals.each { vertical ->
if (vertical != 'erp-core') {
echo "Ejecutando migraciones de ${vertical}..."
runMigrations(vertical)
}
}
}
}
}
// =====================================================================
// DEPLOY
// =====================================================================
stage('Deploy to Staging') {
when {
allOf {
branch 'develop'
expression { return params.ENVIRONMENT == 'staging' }
}
}
steps {
script {
deployVerticals('staging')
}
}
}
stage('Deploy to Production') {
when {
allOf {
branch 'main'
expression { return params.ENVIRONMENT == 'production' }
}
}
steps {
input message: '¿Confirmar despliegue a PRODUCCIÓN?', ok: 'Desplegar'
script {
deployVerticals('production')
}
}
}
// =====================================================================
// HEALTH CHECKS
// =====================================================================
stage('Health Checks') {
steps {
script {
def verticals = env.VERTICALS_TO_BUILD.split(',')
verticals.each { vertical ->
def config = getVerticalConfig(vertical)
def healthUrl = "http://${DEPLOY_SERVER}:${config.backendPort}/health"
retry(5) {
sleep(time: 10, unit: 'SECONDS')
def response = sh(script: "curl -sf ${healthUrl}", returnStatus: true)
if (response != 0) {
error "Health check failed for ${vertical}"
}
}
echo "✅ ${vertical} is healthy"
}
}
}
}
}
// =========================================================================
// POST ACTIONS
// =========================================================================
post {
success {
script {
def message = """
✅ *ERP-Suite Deploy Exitoso*
• *Verticales:* ${env.VERTICALS_TO_BUILD}
• *Ambiente:* ${params.ENVIRONMENT}
• *Build:* #${BUILD_NUMBER}
• *Commit:* ${GIT_COMMIT_SHORT}
""".stripIndent()
echo message
// slackSend(color: 'good', message: message)
}
}
failure {
script {
def message = """
❌ *ERP-Suite Deploy Fallido*
• *Verticales:* ${env.VERTICALS_TO_BUILD}
• *Build:* #${BUILD_NUMBER}
• *Console:* ${BUILD_URL}console
""".stripIndent()
echo message
// slackSend(color: 'danger', message: message)
}
}
always {
cleanWs()
}
}
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
def getVerticalConfig(String vertical) {
def configs = [
'erp-core': [
path: 'apps/erp-core',
frontendPath: 'apps/erp-core/frontend',
frontendPort: 3010,
backendPort: 3011,
dbSchema: 'auth,core,inventory',
active: true
],
'construccion': [
path: 'apps/verticales/construccion',
frontendPath: 'apps/verticales/construccion/frontend/web',
frontendPort: 3020,
backendPort: 3021,
dbSchema: 'construccion',
active: true
],
'vidrio-templado': [
path: 'apps/verticales/vidrio-templado',
frontendPort: 3030,
backendPort: 3031,
dbSchema: 'vidrio',
active: false
],
'mecanicas-diesel': [
path: 'apps/verticales/mecanicas-diesel',
frontendPort: 3040,
backendPort: 3041,
dbSchema: 'service_management,parts_management,vehicle_management',
active: true
],
'retail': [
path: 'apps/verticales/retail',
frontendPort: 3050,
backendPort: 3051,
dbSchema: 'retail',
active: false
],
'clinicas': [
path: 'apps/verticales/clinicas',
frontendPort: 3060,
backendPort: 3061,
dbSchema: 'clinicas',
active: false
],
'pos-micro': [
path: 'apps/products/pos-micro',
frontendPort: 3070,
backendPort: 3071,
dbSchema: 'pos',
active: false
]
]
return configs[vertical] ?: error("Vertical ${vertical} no configurada")
}
def buildVertical(String vertical) {
def config = getVerticalConfig(vertical)
stage("Install ${vertical}") {
if (fileExists("${config.path}/backend/package.json")) {
dir("${config.path}/backend") {
sh 'npm ci --prefer-offline'
}
}
def frontendPath = config.frontendPath ?: "${config.path}/frontend"
if (fileExists("${frontendPath}/package.json")) {
dir(frontendPath) {
sh 'npm ci --prefer-offline'
}
}
}
stage("Build ${vertical}") {
if (fileExists("${config.path}/backend/package.json")) {
dir("${config.path}/backend") {
sh 'npm run build'
}
}
def frontendPath = config.frontendPath ?: "${config.path}/frontend"
if (fileExists("${frontendPath}/package.json")) {
dir(frontendPath) {
sh 'npm run build'
}
}
}
}
def testVertical(String vertical) {
def config = getVerticalConfig(vertical)
if (fileExists("${config.path}/backend/package.json")) {
dir("${config.path}/backend") {
sh 'npm run test || true'
sh 'npm run lint || true'
}
}
}
def runMigrations(String vertical) {
def config = getVerticalConfig(vertical)
sshagent(['deploy-ssh-key']) {
sh """
ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_SERVER} '
cd ${DEPLOY_PATH}/${vertical}
docker-compose exec -T backend npm run migration:run || true
'
"""
}
}
def deployVerticals(String environment) {
def verticals = env.VERTICALS_TO_BUILD.split(',')
sshagent(['deploy-ssh-key']) {
// Desplegar erp-core primero si está en la lista
if (verticals.contains('erp-core')) {
deployVertical('erp-core', environment)
}
// Luego el resto de verticales
verticals.each { vertical ->
if (vertical != 'erp-core') {
deployVertical(vertical, environment)
}
}
}
}
def deployVertical(String vertical, String environment) {
def config = getVerticalConfig(vertical)
echo "Desplegando ${vertical} a ${environment}..."
sh """
ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_SERVER} '
cd ${DEPLOY_PATH}/${vertical}
# Pull nuevas imágenes
docker-compose -f docker-compose.prod.yml pull
# Detener contenedores actuales
docker-compose -f docker-compose.prod.yml down --remove-orphans
# Iniciar nuevos contenedores
docker-compose -f docker-compose.prod.yml up -d
# Cleanup
docker system prune -f
echo "✅ ${vertical} desplegado"
'
"""
}