450 lines
15 KiB
Groovy
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"
|
|
'
|
|
"""
|
|
}
|