255 lines
8.8 KiB
Groovy
255 lines
8.8 KiB
Groovy
pipeline {
|
|
agent any
|
|
options {
|
|
timestamps()
|
|
disableConcurrentBuilds()
|
|
}
|
|
|
|
parameters {
|
|
choice(name: 'ENVIRONMENT', choices: ['development', 'main'], description: 'Rama a ejecutar')
|
|
string(name: 'VERSION_MAYOR', defaultValue: '1', description: 'Componente mayor de versión')
|
|
}
|
|
|
|
environment {
|
|
REGISTRY = 'dev.aduanasoft.com'
|
|
IMAGE_NAMESPACE = 'anexo76'
|
|
}
|
|
|
|
stages {
|
|
stage('Preflight tools') {
|
|
steps {
|
|
sh '''
|
|
set -euo pipefail
|
|
echo "Node: $(hostname)"
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
echo "ERROR: este agente no tiene docker instalado."
|
|
echo "Usa un nodo Jenkins con label 'docker' y acceso al daemon."
|
|
exit 1
|
|
fi
|
|
docker --version
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Checkout') {
|
|
steps {
|
|
checkout([
|
|
$class: 'GitSCM',
|
|
branches: [[name: "*/${params.ENVIRONMENT}"]],
|
|
userRemoteConfigs: [[
|
|
url: 'https://git.aduanasoft.com/ADUANASOFT/anexo76.git',
|
|
credentialsId: 'gitea_acazares'
|
|
]],
|
|
extensions: [[$class: 'CloneOption', depth: 0, shallow: false]]
|
|
])
|
|
}
|
|
}
|
|
|
|
stage('Test backend') {
|
|
steps {
|
|
sh '''
|
|
set -euxo pipefail
|
|
echo "== Test backend stage started =="
|
|
echo "Workspace: $WORKSPACE"
|
|
docker --version
|
|
|
|
DB_CONTAINER="anexo76-test-db-${BUILD_NUMBER}"
|
|
DB_NAME="anexo76_test"
|
|
DB_USER="anexo76"
|
|
DB_PASS="anexo76"
|
|
DB_PORT="55432"
|
|
export TEST_DATABASE_URL="postgresql://${DB_USER}:${DB_PASS}@${DB_CONTAINER}:5432/${DB_NAME}"
|
|
PY_CONTAINER="anexo76-test-py-${BUILD_NUMBER}"
|
|
TEST_NETWORK="anexo76-test-net-${BUILD_NUMBER}"
|
|
|
|
cleanup() {
|
|
docker rm -f "$PY_CONTAINER" >/dev/null 2>&1 || true
|
|
docker rm -f "$DB_CONTAINER" >/dev/null 2>&1 || true
|
|
docker network rm "$TEST_NETWORK" >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
docker network rm "$TEST_NETWORK" >/dev/null 2>&1 || true
|
|
docker network create "$TEST_NETWORK"
|
|
|
|
docker rm -f "$DB_CONTAINER" >/dev/null 2>&1 || true
|
|
docker run -d --name "$DB_CONTAINER" \
|
|
--network "$TEST_NETWORK" \
|
|
-e POSTGRES_DB="$DB_NAME" \
|
|
-e POSTGRES_USER="$DB_USER" \
|
|
-e POSTGRES_PASSWORD="$DB_PASS" \
|
|
postgres:16-alpine
|
|
|
|
# Espera a que Postgres acepte conexiones
|
|
for i in $(seq 1 30); do
|
|
if docker exec "$DB_CONTAINER" pg_isready -U "$DB_USER" -d "$DB_NAME" >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
docker exec "$DB_CONTAINER" pg_isready -U "$DB_USER" -d "$DB_NAME"
|
|
docker exec "$DB_CONTAINER" psql -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 -c "
|
|
CREATE SCHEMA IF NOT EXISTS core;
|
|
CREATE SCHEMA IF NOT EXISTS a24;
|
|
CREATE SCHEMA IF NOT EXISTS a76;
|
|
CREATE SCHEMA IF NOT EXISTS public;
|
|
"
|
|
|
|
docker rm -f "$PY_CONTAINER" >/dev/null 2>&1 || true
|
|
docker run -d --name "$PY_CONTAINER" --network "$TEST_NETWORK" python:3.12-slim sleep infinity
|
|
docker exec "$PY_CONTAINER" mkdir -p /workspace
|
|
docker cp "$WORKSPACE/." "$PY_CONTAINER:/workspace"
|
|
|
|
docker exec \
|
|
-e TEST_DATABASE_URL="$TEST_DATABASE_URL" \
|
|
"$PY_CONTAINER" \
|
|
sh -lc '
|
|
set -euxo pipefail
|
|
python --version
|
|
python -m pip install --upgrade pip
|
|
if [ -f /workspace/backend/requirements.txt ]; then
|
|
pip install -r /workspace/backend/requirements.txt
|
|
elif [ -f /workspace/backend/requirements/base.txt ]; then
|
|
pip install -r /workspace/backend/requirements/base.txt
|
|
else
|
|
echo "ERROR: No requirements file found in /workspace/backend"
|
|
ls -la /workspace || true
|
|
ls -la /workspace/backend || true
|
|
ls -la /workspace/backend/requirements || true
|
|
exit 1
|
|
fi
|
|
cd /workspace/backend
|
|
alembic upgrade head
|
|
pytest -q tests -v -ra -s
|
|
'
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Generate version') {
|
|
steps {
|
|
script {
|
|
def year = sh(returnStdout: true, script: 'date +%y').trim()
|
|
def month = sh(returnStdout: true, script: 'date +%m').trim()
|
|
|
|
if (params.ENVIRONMENT == 'development') {
|
|
def shortHash = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim()
|
|
env.APP_VERSION = "${year}.${month}.${params.VERSION_MAYOR}.${shortHash}"
|
|
} else {
|
|
def lastTag = sh(returnStdout: true, script: 'git describe --tags --abbrev=0 2>/dev/null || true').trim()
|
|
def commitCount = lastTag
|
|
? sh(returnStdout: true, script: "git rev-list ${lastTag}..HEAD --count").trim()
|
|
: sh(returnStdout: true, script: 'git rev-list --count HEAD').trim()
|
|
|
|
if (commitCount == '0') {
|
|
commitCount = '1'
|
|
}
|
|
env.APP_VERSION = "${year}.${month}.${params.VERSION_MAYOR}.${commitCount}"
|
|
}
|
|
|
|
echo "VERSION: ${env.APP_VERSION}"
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Docker login') {
|
|
steps {
|
|
withCredentials([usernamePassword(credentialsId: 'harbor-credentials', usernameVariable: 'HARBOR_USERNAME', passwordVariable: 'HARBOR_PASSWORD')]) {
|
|
sh '''
|
|
set -euo pipefail
|
|
echo "$HARBOR_PASSWORD" | docker login "$REGISTRY" -u "$HARBOR_USERNAME" --password-stdin
|
|
'''
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Build + push backend') {
|
|
steps {
|
|
sh '''
|
|
set -euo pipefail
|
|
docker build \
|
|
--build-arg APP_VERSION="${APP_VERSION}" \
|
|
-t "${REGISTRY}/${IMAGE_NAMESPACE}/backend:latest" \
|
|
-f ./backend/Dockerfile \
|
|
./backend
|
|
|
|
docker push "${REGISTRY}/${IMAGE_NAMESPACE}/backend:latest"
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Build + push frontend') {
|
|
steps {
|
|
sh '''
|
|
set -euo pipefail
|
|
docker build \
|
|
--build-arg VITE_API_URL=https://anexo76-dev.aduanasoft.com/api/ \
|
|
--build-arg VITE_KEYCLOAK_URL=https://anexo76-dev.aduanasoft.com/kcauth/ \
|
|
--build-arg INTERNAL_API_URL=http://backend:3467/api/ \
|
|
-t "${REGISTRY}/${IMAGE_NAMESPACE}/frontend:latest" \
|
|
-f ./frontend/Dockerfile.prod \
|
|
./frontend
|
|
|
|
docker push "${REGISTRY}/${IMAGE_NAMESPACE}/frontend:latest"
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Tag release (main)') {
|
|
when {
|
|
expression { params.ENVIRONMENT == 'main' }
|
|
}
|
|
steps {
|
|
withCredentials([usernamePassword(credentialsId: 'gitea_acazares', usernameVariable: 'GIT_USERNAME', passwordVariable: 'GIT_PASSWORD')]) {
|
|
sh '''
|
|
set -euo pipefail
|
|
git -c user.name='Jenkins' -c user.email='jenkins@gitea.local' \
|
|
tag -a "v${APP_VERSION}" -m "Release version ${APP_VERSION}"
|
|
git remote set-url origin "https://${GIT_USERNAME}:${GIT_PASSWORD}@git.aduanasoft.com/ADUANASOFT/anexo76.git"
|
|
git push origin "v${APP_VERSION}"
|
|
'''
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Deploy development') {
|
|
when {
|
|
expression { params.ENVIRONMENT == 'development' }
|
|
}
|
|
steps {
|
|
withCredentials([
|
|
usernamePassword(credentialsId: 'harbor-credentials', usernameVariable: 'HARBOR_USERNAME', passwordVariable: 'HARBOR_PASSWORD'),
|
|
string(credentialsId: 'dev-server-user', variable: 'DEV_SERVER_USER'),
|
|
string(credentialsId: 'dev-server-host', variable: 'DEV_SERVER_HOST'),
|
|
sshUserPrivateKey(
|
|
credentialsId: 'dev-server-ssh',
|
|
keyFileVariable: 'DEV_SERVER_KEY',
|
|
usernameVariable: 'DEV_SERVER_SSH_USER',
|
|
passphraseVariable: 'DEV_SERVER_KEY_PASSPHRASE'
|
|
)
|
|
]) {
|
|
script {
|
|
def remote = [
|
|
name: 'dev-server',
|
|
host: env.DEV_SERVER_HOST,
|
|
user: env.DEV_SERVER_USER ?: env.DEV_SERVER_SSH_USER,
|
|
identityFile: env.DEV_SERVER_KEY,
|
|
passphrase: env.DEV_SERVER_KEY_PASSPHRASE,
|
|
allowAnyHosts: true
|
|
]
|
|
sshCommand remote: remote, command: '''
|
|
set -euo pipefail
|
|
cd ~/projects/anexo76
|
|
# Asume docker login ya configurado en el host destino para evitar exponer secretos por interpolación.
|
|
docker compose -f docker-compose.prod.yml pull
|
|
docker compose -f docker-compose.prod.yml up -d
|
|
docker image prune -f
|
|
'''
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|