perf(ci): paralelizar tests/security/builds y cachear deps entre runs

Optimizaciones aplicadas al pipeline para reducir tiempo total ~5-10 min:

1. Tests + Security Scan fusionados en un único stage paralelo de 4
   substages: Test Backend, Test Frontend Vitest, pip-audit, npm audit.
   Antes secuencial (Tests → Security): ahora máximo de los 4 en vez de
   la suma. Ahorro: ~30-60s.

2. Docker Build de backend y frontend paralelizado en substages propios.
   Antes secuencial (~6-10 min totales), ahora ~max(3-5min). Cada substage
   hace su propio docker login (cheap, ~1s). Ahorro: ~3-5 min.

3. --cache-from + BUILDKIT_INLINE_CACHE=1 en ambos builds de Harbor.
   docker pull ${IMG}:latest antes del build alimenta el cache. Las
   layers de pip install / pnpm install se reusan cuando los lockfiles
   no cambian. Ahorro: ~1-3 min por imagen.

4. Volumes Docker nombrados (jenkins-a76-pip-cache, jenkins-a76-pnpm-store)
   en los contenedores de Test Backend, Test Frontend Vitest y pip-audit.
   Persisten entre builds en el mismo agente Jenkins; primer build los
   puebla, siguientes reusan paquetes/wheels. Ahorro: ~30-60s por stage.

Estimado total: pipeline pasa de ~25-30min a ~15-20min.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-22 14:20:01 -05:00
parent 6e4e5d58ef
commit 870d91d8f3

136
Jenkinsfile vendored
View File

@@ -1,5 +1,5 @@
// Pipeline CI/CD — Aduanasoft Anexo76
// Ejecuta: testssecurity scan → docker build/push → E2E → deploy dev → smoke test
// Ejecuta: tests+security (paralelo) → docker build/push (paralelo) → E2E → deploy dev → smoke test
//
// Credenciales requeridas en Jenkins:
// - harbor-credentials : username/password para dev.aduanasoft.com
@@ -37,8 +37,13 @@ pipeline {
stages {
// ── Tests (backend y frontend unit en paralelo) ───────────────────────
stage('Tests') {
// ── Tests + Security (4 trabajos en paralelo) ─────────────────────────
// Backend tests, frontend Vitest, pip-audit y npm audit son independientes:
// todos en un solo bloque parallel — la stage tarda el máximo de los 4,
// no la suma. pip y pnpm cachean en volumes persistentes del agente
// (jenkins-a76-pip-cache, jenkins-a76-pnpm-store) para reusar deps
// entre builds (~30-60s menos cada uno cuando lockfiles no cambian).
stage('Tests & Security') {
parallel {
stage('Test — Backend') {
@@ -92,7 +97,11 @@ pipeline {
"
docker rm -f "$C" >/dev/null 2>&1 || true
docker run -d --name "$C" --network "$NET" "$PYTHON_IMAGE" sleep infinity
# Volume persistente para el cache de pip (~/.cache/pip):
# primer build lo puebla, siguientes reusan wheels si requirements.txt no cambió.
docker run -d --name "$C" --network "$NET" \
-v jenkins-a76-pip-cache:/root/.cache/pip \
"$PYTHON_IMAGE" sleep infinity
docker cp "$WORKSPACE/backend/." "$C:/app"
docker exec \
@@ -144,7 +153,12 @@ pipeline {
trap cleanup EXIT
docker rm -f "$C" >/dev/null 2>&1 || true
docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity
# Volume persistente para el store de pnpm (content-addressable):
# primer build lo puebla, siguientes reusan paquetes si pnpm-lock.yaml no cambió.
# Path estándar de pnpm@9 en Linux como root: /root/.local/share/pnpm/store
docker run -d --name "$C" \
-v jenkins-a76-pnpm-store:/root/.local/share/pnpm/store \
"$PLAYWRIGHT_IMAGE" sleep infinity
docker exec "$C" mkdir -p /workspace
docker cp "$WORKSPACE/." "$C:/workspace"
@@ -165,12 +179,6 @@ pipeline {
}
}
}
}
// ── Escaneo de vulnerabilidades ───────────────────────────────────────
stage('Security Scan') {
parallel {
stage('pip-audit') {
steps {
sh '''
@@ -180,7 +188,9 @@ pipeline {
trap cleanup EXIT
docker rm -f "$C" >/dev/null 2>&1 || true
docker run -d --name "$C" "$PYTHON_IMAGE" sleep infinity
docker run -d --name "$C" \
-v jenkins-a76-pip-cache:/root/.cache/pip \
"$PYTHON_IMAGE" sleep infinity
docker cp "$WORKSPACE/backend/requirements.txt" "$C:/requirements.txt"
docker exec "$C" bash -lc '
@@ -212,6 +222,7 @@ pipeline {
'''
}
}
}
}
@@ -243,55 +254,86 @@ pipeline {
}
}
// ── Docker Build + Push ───────────────────────────────────────────────
// ── Docker Build + Push (backend y frontend en paralelo) ──────────────
// Antes era secuencial: ~6-10 min totales. Ahora cada imagen builda+pushea
// en su propio substage en paralelo → tarda el máximo de los dos.
// BuildKit inline cache + --cache-from reusan las layers de :latest cuando
// pip-lock / pnpm-lock no cambiaron (~1-3 min menos por imagen).
stage('Docker Build') {
when {
anyOf { branch 'main'; branch 'development' }
}
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'harbor-credentials',
usernameVariable: 'HARBOR_USER',
passwordVariable: 'HARBOR_PASS'
)
]) {
sh "echo \"\${HARBOR_PASS}\" | docker login ${HARBOR_REGISTRY} -u \"\${HARBOR_USER}\" --password-stdin"
sh """
docker build --progress=plain \
--build-arg APP_VERSION=${env.APP_VERSION} \
-t ${IMAGE_BACKEND}:${env.APP_VERSION} \
-t ${IMAGE_BACKEND}:latest \
-f backend/Dockerfile \
backend/
"""
parallel {
stage('Build & Push — Backend') {
steps {
withCredentials([
usernamePassword(
credentialsId: 'harbor-credentials',
usernameVariable: 'HARBOR_USER',
passwordVariable: 'HARBOR_PASS'
)
]) {
sh """
set -euo pipefail
export DOCKER_BUILDKIT=1
echo "\${HARBOR_PASS}" | docker login ${HARBOR_REGISTRY} -u "\${HARBOR_USER}" --password-stdin
# Pull de :latest para alimentar --cache-from (silencioso si no existe)
docker pull ${IMAGE_BACKEND}:latest >/dev/null 2>&1 || true
docker build --progress=plain \\
--cache-from ${IMAGE_BACKEND}:latest \\
--build-arg BUILDKIT_INLINE_CACHE=1 \\
--build-arg APP_VERSION=${env.APP_VERSION} \\
-t ${IMAGE_BACKEND}:${env.APP_VERSION} \\
-t ${IMAGE_BACKEND}:latest \\
-f backend/Dockerfile \\
backend/
docker push ${IMAGE_BACKEND}:${env.APP_VERSION}
docker push ${IMAGE_BACKEND}:latest
"""
}
}
}
stage('Build & Push — Frontend') {
steps {
withCredentials([
usernamePassword(
credentialsId: 'harbor-credentials',
usernameVariable: 'HARBOR_USER',
passwordVariable: 'HARBOR_PASS'
),
string(credentialsId: 'a76-public-url-dev', variable: 'A76_URL')
]) {
sh """
docker build --progress=plain \
--build-arg VITE_API_URL=\${A76_URL}/api/ \
--build-arg VITE_KEYCLOAK_URL=${KC_URL} \
--build-arg VITE_KEYCLOAK_CLIENT_ID=anexo76-frontend \
--build-arg INTERNAL_API_URL=http://backend:3467/api/ \
-t ${IMAGE_FRONTEND}:${env.APP_VERSION} \
-t ${IMAGE_FRONTEND}:latest \
-f frontend/Dockerfile.prod \
set -euo pipefail
export DOCKER_BUILDKIT=1
echo "\${HARBOR_PASS}" | docker login ${HARBOR_REGISTRY} -u "\${HARBOR_USER}" --password-stdin
docker pull ${IMAGE_FRONTEND}:latest >/dev/null 2>&1 || true
docker build --progress=plain \\
--cache-from ${IMAGE_FRONTEND}:latest \\
--build-arg BUILDKIT_INLINE_CACHE=1 \\
--build-arg VITE_API_URL=\${A76_URL}/api/ \\
--build-arg VITE_KEYCLOAK_URL=${KC_URL} \\
--build-arg VITE_KEYCLOAK_CLIENT_ID=anexo76-frontend \\
--build-arg INTERNAL_API_URL=http://backend:3467/api/ \\
-t ${IMAGE_FRONTEND}:${env.APP_VERSION} \\
-t ${IMAGE_FRONTEND}:latest \\
-f frontend/Dockerfile.prod \\
frontend/
docker push ${IMAGE_FRONTEND}:${env.APP_VERSION}
docker push ${IMAGE_FRONTEND}:latest
"""
}
sh """
docker push ${IMAGE_BACKEND}:${env.APP_VERSION}
docker push ${IMAGE_BACKEND}:latest
docker push ${IMAGE_FRONTEND}:${env.APP_VERSION}
docker push ${IMAGE_FRONTEND}:latest
"""
}
}
}
}