Merge pull request 'feature/ci-pipeline-speedup' (#446) from feature/ci-pipeline-speedup into development
Reviewed-on: ADUANASOFT/anexo76#446
This commit is contained in:
147
Jenkinsfile
vendored
147
Jenkinsfile
vendored
@@ -1,5 +1,5 @@
|
||||
// Pipeline CI/CD — Aduanasoft Anexo76
|
||||
// Ejecuta: tests → security 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
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,9 +502,16 @@ pipeline {
|
||||
echo "✓ Playwright listo"
|
||||
|
||||
# ── Correr tests ────────────────────────────────────────────────
|
||||
# --reporter=list,junit,html:
|
||||
# - list → progreso por test a stdout (visible en Jenkins)
|
||||
# - junit → archivo XML para junit() plugin
|
||||
# - html → carpeta playwright-report con UI
|
||||
# stdbuf -oL fuerza buffer por líneas para que Jenkins vea el output
|
||||
# incremental aunque pnpm/playwright bufferen.
|
||||
E2E_EXIT=0
|
||||
docker exec \
|
||||
-e CI=true \
|
||||
-e FORCE_COLOR=0 \
|
||||
-e "JENKINS_URL=${JENKINS_URL}" \
|
||||
-e "PLAYWRIGHT_TEST_BASE_URL=http://localhost:5173" \
|
||||
-e "E2E_TEST_USER=${E2E_USER}" \
|
||||
@@ -470,8 +519,8 @@ pipeline {
|
||||
-e "PLAYWRIGHT_JUNIT_OUTPUT_NAME=playwright-results.xml" \
|
||||
-w /workspace/frontend \
|
||||
"$PLAYWRIGHT_C" \
|
||||
pnpm exec playwright test \
|
||||
--reporter=junit,html \
|
||||
stdbuf -oL -eL pnpm exec playwright test \
|
||||
--reporter=list,junit,html \
|
||||
--trace=retain-on-failure || E2E_EXIT=$?
|
||||
|
||||
# Copiar artefactos antes de salir (pase o falle)
|
||||
|
||||
@@ -134,6 +134,17 @@ test.describe('Setup — Catalogos para pruebas E2E', () => {
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
// PrerequisitesModal: AlertDialog "Aviso" que abre cuando la DB no tiene
|
||||
// Agentes aduanales o Clientes registrados (caso típico en E2E con DB limpia).
|
||||
// Bloquea pointer events con un overlay sobre todo el form. Cancelar te saca
|
||||
// del editor; Aceptar lo cierra y permite continuar.
|
||||
// Ver routes/dashboard/goods/parts/edit/[[id]]/+page.svelte:29-38.
|
||||
const prerequisitesDialog = page.getByRole('alertdialog', { name: /Aviso/i })
|
||||
if (await prerequisitesDialog.isVisible({ timeout: 2000 }).catch(() => false)) {
|
||||
await prerequisitesDialog.getByRole('button', { name: /^Aceptar$/ }).click()
|
||||
await prerequisitesDialog.waitFor({ state: 'hidden', timeout: 5000 }).catch(() => {})
|
||||
}
|
||||
|
||||
// Llenar campos del formulario de nueva parte
|
||||
// Numero de parte
|
||||
const partInput = page.locator('#part_number, input[placeholder*="parte"], input[placeholder*="número"], input[name="part_number"]').first()
|
||||
|
||||
Reference in New Issue
Block a user