Merge pull request 'refactor(jenkins): update CI/CD pipeline to run tests in parallel and remove linting stages' (#412) from fix/jenkins-mejora into development
Reviewed-on: ADUANASOFT/anexo76#412
This commit is contained in:
344
Jenkinsfile
vendored
344
Jenkinsfile
vendored
@@ -1,5 +1,5 @@
|
||||
// Pipeline CI/CD — Aduanasoft Anexo76
|
||||
// Ejecuta: lint → tests (unit) → security scan → docker build/push → deploy dev → smoke test
|
||||
// Ejecuta: tests (backend + frontend en paralelo) → security scan → docker build/push → deploy dev → smoke test
|
||||
//
|
||||
// Credenciales requeridas en Jenkins:
|
||||
// - harbor-credentials : username/password para dev.aduanasoft.com
|
||||
@@ -33,162 +33,216 @@ pipeline {
|
||||
|
||||
stages {
|
||||
|
||||
// ── Paso 5a — Lint ────────────────────────────────────────────────────
|
||||
stage('Lint — Backend') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
C="a76-lint-py-${BUILD_NUMBER}"
|
||||
cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; }
|
||||
trap cleanup EXIT
|
||||
// ── Paso 5b — Tests (backend y frontend en paralelo) ──────────────────
|
||||
stage('Tests') {
|
||||
parallel {
|
||||
|
||||
docker rm -f "$C" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$C" "$PYTHON_IMAGE" sleep infinity
|
||||
docker cp "$WORKSPACE/backend/." "$C:/app"
|
||||
stage('Test — Backend') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
C="a76-test-py-${BUILD_NUMBER}"
|
||||
DB_C="a76-test-db-${BUILD_NUMBER}"
|
||||
NET="a76-test-net-${BUILD_NUMBER}"
|
||||
DB_NAME="anexo76_test"
|
||||
DB_USER="anexo76"
|
||||
DB_PASS="anexo76"
|
||||
TEST_DB_URL="postgresql://${DB_USER}:${DB_PASS}@${DB_C}:5432/${DB_NAME}"
|
||||
|
||||
docker exec -w /app "$C" bash -lc '
|
||||
set -euxo pipefail
|
||||
pip install --quiet "ruff==0.6.0"
|
||||
ruff check . --exclude venv
|
||||
ruff format --check . --exclude venv
|
||||
'
|
||||
'''
|
||||
}
|
||||
}
|
||||
cleanup() {
|
||||
docker rm -f "$C" >/dev/null 2>&1 || true
|
||||
docker rm -f "$DB_C" >/dev/null 2>&1 || true
|
||||
docker network rm "$NET" >/dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
stage('Lint — Frontend') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
C="a76-lint-fe-${BUILD_NUMBER}"
|
||||
cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; }
|
||||
trap cleanup EXIT
|
||||
docker network rm "$NET" >/dev/null 2>&1 || true
|
||||
docker network create "$NET"
|
||||
|
||||
docker rm -f "$C" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity
|
||||
docker exec "$C" mkdir -p /workspace
|
||||
docker cp "$WORKSPACE/." "$C:/workspace"
|
||||
docker rm -f "$DB_C" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$DB_C" \
|
||||
--network "$NET" \
|
||||
-e POSTGRES_DB="$DB_NAME" \
|
||||
-e POSTGRES_USER="$DB_USER" \
|
||||
-e POSTGRES_PASSWORD="$DB_PASS" \
|
||||
postgres:16-alpine
|
||||
|
||||
docker exec -w /workspace/frontend "$C" bash -lc '
|
||||
set -euxo pipefail
|
||||
npm install -g pnpm@9 --quiet
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run lint
|
||||
pnpm run i18n:compile
|
||||
pnpm run check
|
||||
'
|
||||
'''
|
||||
}
|
||||
}
|
||||
# Esperar a que Postgres esté listo
|
||||
READY=0
|
||||
for i in $(seq 1 30); do
|
||||
if docker exec "$DB_C" pg_isready -U "$DB_USER" -d "$DB_NAME" >/dev/null 2>&1; then
|
||||
READY=1; break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
if [ "$READY" != "1" ]; then
|
||||
echo "ERROR: Postgres no quedó listo en 60s."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
// ── Paso 5b — Tests ───────────────────────────────────────────────────
|
||||
stage('Test — Backend') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
C="a76-test-py-${BUILD_NUMBER}"
|
||||
DB_C="a76-test-db-${BUILD_NUMBER}"
|
||||
NET="a76-test-net-${BUILD_NUMBER}"
|
||||
DB_NAME="anexo76_test"
|
||||
DB_USER="anexo76"
|
||||
DB_PASS="anexo76"
|
||||
TEST_DB_URL="postgresql://${DB_USER}:${DB_PASS}@${DB_C}:5432/${DB_NAME}"
|
||||
docker exec "$DB_C" 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;
|
||||
"
|
||||
|
||||
cleanup() {
|
||||
docker rm -f "$C" >/dev/null 2>&1 || true
|
||||
docker rm -f "$DB_C" >/dev/null 2>&1 || true
|
||||
docker network rm "$NET" >/dev/null 2>&1 || true
|
||||
docker rm -f "$C" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$C" --network "$NET" "$PYTHON_IMAGE" sleep infinity
|
||||
docker cp "$WORKSPACE/backend/." "$C:/app"
|
||||
|
||||
docker exec \
|
||||
-e TEST_DATABASE_URL="$TEST_DB_URL" \
|
||||
-w /app "$C" bash -lc '
|
||||
set -euxo pipefail
|
||||
pip install --quiet -r requirements.txt
|
||||
|
||||
# Prueba de migraciones: upgrade → downgrade → upgrade
|
||||
# Verifica que todos los down() sean reversibles
|
||||
echo "--- alembic upgrade head ---"
|
||||
alembic upgrade head
|
||||
echo "--- alembic downgrade base ---"
|
||||
alembic downgrade base
|
||||
echo "--- alembic upgrade head (restaurar para tests) ---"
|
||||
alembic upgrade head
|
||||
|
||||
pytest tests/ \
|
||||
--cov=. \
|
||||
--cov-fail-under=80 \
|
||||
--cov-report=term-missing \
|
||||
--cov-report=xml \
|
||||
--junitxml=test-results.xml \
|
||||
-v -ra
|
||||
'
|
||||
docker cp "$C:/app/test-results.xml" "$WORKSPACE/backend/test-results.xml"
|
||||
docker cp "$C:/app/coverage.xml" "$WORKSPACE/backend/coverage.xml"
|
||||
'''
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit allowEmptyResults: true, testResults: 'backend/test-results.xml'
|
||||
archiveArtifacts artifacts: 'backend/coverage.xml', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
docker network rm "$NET" >/dev/null 2>&1 || true
|
||||
docker network create "$NET"
|
||||
|
||||
docker rm -f "$DB_C" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$DB_C" \
|
||||
--network "$NET" \
|
||||
-e POSTGRES_DB="$DB_NAME" \
|
||||
-e POSTGRES_USER="$DB_USER" \
|
||||
-e POSTGRES_PASSWORD="$DB_PASS" \
|
||||
postgres:16-alpine
|
||||
|
||||
# Esperar a que Postgres esté listo
|
||||
READY=0
|
||||
for i in $(seq 1 30); do
|
||||
if docker exec "$DB_C" pg_isready -U "$DB_USER" -d "$DB_NAME" >/dev/null 2>&1; then
|
||||
READY=1; break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
if [ "$READY" != "1" ]; then
|
||||
echo "ERROR: Postgres no quedó listo en 60s."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker exec "$DB_C" 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 "$C" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$C" --network "$NET" "$PYTHON_IMAGE" sleep infinity
|
||||
docker cp "$WORKSPACE/backend/." "$C:/app"
|
||||
|
||||
docker exec \
|
||||
-e TEST_DATABASE_URL="$TEST_DB_URL" \
|
||||
-w /app "$C" bash -lc '
|
||||
set -euxo pipefail
|
||||
pip install --quiet -r requirements.txt
|
||||
alembic upgrade head
|
||||
pytest tests/ \
|
||||
--cov=. \
|
||||
--cov-fail-under=80 \
|
||||
--cov-report=term-missing \
|
||||
--cov-report=xml \
|
||||
--junitxml=test-results.xml \
|
||||
-v -ra
|
||||
'
|
||||
docker cp "$C:/app/test-results.xml" "$WORKSPACE/backend/test-results.xml"
|
||||
docker cp "$C:/app/coverage.xml" "$WORKSPACE/backend/coverage.xml"
|
||||
'''
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit allowEmptyResults: true, testResults: 'backend/test-results.xml'
|
||||
archiveArtifacts artifacts: 'backend/coverage.xml', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Test — Frontend') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
C="a76-test-fe-${BUILD_NUMBER}"
|
||||
cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; }
|
||||
trap cleanup EXIT
|
||||
stage('Test — Frontend') {
|
||||
stages {
|
||||
|
||||
docker rm -f "$C" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity
|
||||
docker exec "$C" mkdir -p /workspace
|
||||
docker cp "$WORKSPACE/." "$C:/workspace"
|
||||
stage('Unit Tests (Vitest)') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
C="a76-test-fe-${BUILD_NUMBER}"
|
||||
cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; }
|
||||
trap cleanup EXIT
|
||||
|
||||
docker rm -f "$C" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity
|
||||
docker exec "$C" mkdir -p /workspace
|
||||
docker cp "$WORKSPACE/." "$C:/workspace"
|
||||
|
||||
docker exec \
|
||||
-e CI=true \
|
||||
-e NODE_ENV=test \
|
||||
-e "JENKINS_URL=${JENKINS_URL}" \
|
||||
-e JENKINS_VITEST_FULL=1 \
|
||||
-w /workspace/frontend \
|
||||
"$C" bash -lc '
|
||||
set -euxo pipefail
|
||||
npm install -g pnpm@9 --quiet
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run i18n:compile
|
||||
pnpm run test:unit -- --run
|
||||
'
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('E2E (Playwright)') {
|
||||
steps {
|
||||
// El dev server (localhost:5173) se conecta al Keycloak/API de test
|
||||
// via las credenciales inyectadas. El frontend corre en el contenedor;
|
||||
// Keycloak y la API son externos (misma instancia que dev).
|
||||
withCredentials([
|
||||
string(credentialsId: 'a76-public-url-dev', variable: 'A76_URL')
|
||||
]) {
|
||||
sh '''
|
||||
set -euo pipefail
|
||||
C="a76-test-e2e-${BUILD_NUMBER}"
|
||||
cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; }
|
||||
trap cleanup EXIT
|
||||
|
||||
docker rm -f "$C" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity
|
||||
docker exec "$C" mkdir -p /workspace
|
||||
docker cp "$WORKSPACE/." "$C:/workspace"
|
||||
|
||||
docker exec \
|
||||
-e CI=true \
|
||||
-e NODE_ENV=test \
|
||||
-e "JENKINS_URL=${JENKINS_URL}" \
|
||||
-e PLAYWRIGHT_TEST_BASE_URL=http://localhost:5173 \
|
||||
-e "VITE_API_URL=${A76_URL}/api/" \
|
||||
-e "INTERNAL_API_URL=${A76_URL}/api/" \
|
||||
-e "VITE_KEYCLOAK_URL=${A76_URL}/kcauth" \
|
||||
-e "KEYCLOAK_URL=${A76_URL}/kcauth" \
|
||||
-e VITE_KEYCLOAK_REALM=master \
|
||||
-e KEYCLOAK_REALM=master \
|
||||
-e VITE_KEYCLOAK_CLIENT_ID=anexo76-frontend \
|
||||
-e KEYCLOAK_CLIENT_ID=anexo76-frontend \
|
||||
-e ORIGIN=http://localhost:5173 \
|
||||
-w /workspace/frontend \
|
||||
"$C" bash -lc '
|
||||
set -euxo pipefail
|
||||
npm install -g pnpm@9 --quiet
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run i18n:compile
|
||||
|
||||
# Arrancar dev server con las vars ya inyectadas
|
||||
pnpm run dev &
|
||||
DEV_PID=$!
|
||||
|
||||
# Esperar a que el servidor esté listo (máx 60s)
|
||||
READY=0
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf http://localhost:5173 >/dev/null 2>&1; then
|
||||
READY=1; break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
if [ "$READY" != "1" ]; then
|
||||
echo "ERROR: Dev server no arrancó en 60s."
|
||||
kill "$DEV_PID" 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ejecutar E2E; capturar exit code para no perder el reporte
|
||||
E2E_EXIT=0
|
||||
PLAYWRIGHT_JUNIT_OUTPUT_NAME=playwright-results.xml \
|
||||
pnpm exec playwright test --reporter=junit,html || E2E_EXIT=$?
|
||||
|
||||
kill "$DEV_PID" 2>/dev/null || true
|
||||
exit "$E2E_EXIT"
|
||||
'
|
||||
docker cp "$C:/workspace/frontend/playwright-results.xml" \
|
||||
"$WORKSPACE/frontend/playwright-results.xml" 2>/dev/null || true
|
||||
docker cp "$C:/workspace/frontend/playwright-report" \
|
||||
"$WORKSPACE/frontend/playwright-report" 2>/dev/null || true
|
||||
'''
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit allowEmptyResults: true, testResults: 'frontend/playwright-results.xml'
|
||||
archiveArtifacts artifacts: 'frontend/playwright-report/**', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
docker exec \
|
||||
-e CI=true \
|
||||
-e NODE_ENV=test \
|
||||
-e "JENKINS_URL=${JENKINS_URL}" \
|
||||
-e JENKINS_VITEST_FULL=1 \
|
||||
-w /workspace/frontend \
|
||||
"$C" bash -lc '
|
||||
set -euxo pipefail
|
||||
npm install -g pnpm@9 --quiet
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run i18n:compile
|
||||
pnpm run test:unit -- --run
|
||||
'
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user