Refactor Jenkinsfile to streamline CI image build and testing process
- Updated the Jenkinsfile to build backend and frontend images using Docker Compose, enhancing the efficiency of the CI pipeline. - Removed the obsolete e2e_alembic_upgrade.py script, as its functionality is no longer required. - Improved comments for clarity regarding the build and testing stages, ensuring better understanding of the CI process.
This commit is contained in:
372
Jenkinsfile
vendored
372
Jenkinsfile
vendored
@@ -29,6 +29,7 @@ pipeline {
|
||||
exit 1
|
||||
fi
|
||||
docker --version
|
||||
docker compose version
|
||||
'''
|
||||
}
|
||||
}
|
||||
@@ -47,133 +48,144 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
stage('Test backend') {
|
||||
// Build único: ambas imágenes se copian del workspace al daemon vía BuildKit
|
||||
// y se reutilizan en los stages de test y E2E. El override ci quita los bind
|
||||
// mounts (./backend:/app, ./frontend:/app) que el daemon de Jenkins no puede
|
||||
// resolver al no ver $WORKSPACE del agente.
|
||||
stage('Build CI images') {
|
||||
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"
|
||||
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 (hasta ~60s)
|
||||
READY=0
|
||||
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
|
||||
READY=1
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
if [ "$READY" != "1" ]; then
|
||||
echo "ERROR: Postgres no quedó listo a tiempo (30 intentos × 2s)."
|
||||
exit 1
|
||||
fi
|
||||
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
|
||||
'
|
||||
export DOCKER_BUILDKIT=1
|
||||
docker compose \
|
||||
-f "$WORKSPACE/docker-compose.yml" \
|
||||
-f "$WORKSPACE/docker-compose.ci.yml" \
|
||||
build backend frontend
|
||||
docker image inspect anexo76-backend:latest >/dev/null
|
||||
docker image inspect anexo76-frontend:latest >/dev/null
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
// Código = WORKSPACE (docker cp), no la imagen Harbor. Imagen base: mcr.microsoft.com/playwright (Chromium p/ Vitest y E2E).
|
||||
stage('Test frontend (Vitest + Svelte browser)') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euxo pipefail
|
||||
echo "== Test frontend: vitest (Node + @vitest/browser) con $PLAYWRIGHT_TEST_IMAGE =="
|
||||
if [ ! -f "$WORKSPACE/frontend/package.json" ]; then
|
||||
echo "ERROR: $WORKSPACE/frontend/package.json no existe"
|
||||
ls -la "$WORKSPACE" 2>&1 | head -40
|
||||
exit 1
|
||||
fi
|
||||
FE_CONTAINER="anexo76-test-fe-${BUILD_NUMBER}"
|
||||
|
||||
cleanup() {
|
||||
docker rm -f "$FE_CONTAINER" >/dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
docker rm -f "$FE_CONTAINER" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$FE_CONTAINER" "$PLAYWRIGHT_TEST_IMAGE" sleep infinity
|
||||
docker exec "$FE_CONTAINER" mkdir -p /workspace
|
||||
docker cp "$WORKSPACE/." "$FE_CONTAINER:/workspace"
|
||||
|
||||
docker exec \
|
||||
-e CI=true \
|
||||
-e NODE_ENV=test \
|
||||
-e "JENKINS_URL=${JENKINS_URL}" \
|
||||
-e JENKINS_VITEST_FULL=1 \
|
||||
-w /workspace/frontend \
|
||||
"$FE_CONTAINER" \
|
||||
bash -lc '
|
||||
// Feedback rápido en paralelo: si unit rompe, no gastamos el stack E2E.
|
||||
stage('Unit tests') {
|
||||
parallel {
|
||||
// Reutiliza anexo76-backend:latest (ya incluye pytest + alembic + código)
|
||||
stage('Test backend (pytest)') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euxo pipefail
|
||||
test -f package.json
|
||||
node --version
|
||||
corepack enable
|
||||
pnpm --version
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run i18n:compile
|
||||
pnpm run test:unit -- --run
|
||||
'
|
||||
'''
|
||||
echo "== Test backend: pytest en anexo76-backend:latest =="
|
||||
|
||||
DB_CONTAINER="anexo76-test-db-${BUILD_NUMBER}"
|
||||
DB_NAME="anexo76_test"
|
||||
DB_USER="anexo76"
|
||||
DB_PASS="anexo76"
|
||||
PY_CONTAINER="anexo76-test-py-${BUILD_NUMBER}"
|
||||
TEST_NETWORK="anexo76-test-net-${BUILD_NUMBER}"
|
||||
TEST_DATABASE_URL="postgresql://${DB_USER}:${DB_PASS}@${DB_CONTAINER}:5432/${DB_NAME}"
|
||||
|
||||
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
|
||||
|
||||
READY=0
|
||||
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
|
||||
READY=1
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
if [ "$READY" != "1" ]; then
|
||||
echo "ERROR: Postgres no quedó listo a tiempo (30 intentos × 2s)."
|
||||
exit 1
|
||||
fi
|
||||
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;
|
||||
"
|
||||
|
||||
# --entrypoint sleep: evita que docker-entrypoint.sh bloquee esperando Keycloak en 127.0.0.1
|
||||
docker rm -f "$PY_CONTAINER" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$PY_CONTAINER" \
|
||||
--network "$TEST_NETWORK" \
|
||||
--entrypoint sleep \
|
||||
anexo76-backend:latest infinity
|
||||
|
||||
docker exec \
|
||||
-e TEST_DATABASE_URL="$TEST_DATABASE_URL" \
|
||||
-w /app \
|
||||
"$PY_CONTAINER" \
|
||||
sh -lc '
|
||||
set -euxo pipefail
|
||||
python --version
|
||||
alembic upgrade head
|
||||
pytest -q tests -v -ra -s
|
||||
'
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
// Vitest + @vitest/browser necesitan Chromium; imagen Playwright ya lo trae.
|
||||
stage('Test frontend (vitest)') {
|
||||
steps {
|
||||
sh '''
|
||||
set -euxo pipefail
|
||||
echo "== Test frontend: vitest con $PLAYWRIGHT_TEST_IMAGE =="
|
||||
if [ ! -f "$WORKSPACE/frontend/package.json" ]; then
|
||||
echo "ERROR: $WORKSPACE/frontend/package.json no existe"
|
||||
ls -la "$WORKSPACE" 2>&1 | head -40
|
||||
exit 1
|
||||
fi
|
||||
FE_CONTAINER="anexo76-test-fe-${BUILD_NUMBER}"
|
||||
|
||||
cleanup() {
|
||||
docker rm -f "$FE_CONTAINER" >/dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
docker rm -f "$FE_CONTAINER" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$FE_CONTAINER" "$PLAYWRIGHT_TEST_IMAGE" sleep infinity
|
||||
docker exec "$FE_CONTAINER" mkdir -p /workspace
|
||||
docker cp "$WORKSPACE/." "$FE_CONTAINER:/workspace"
|
||||
|
||||
docker exec \
|
||||
-e CI=true \
|
||||
-e NODE_ENV=test \
|
||||
-e "JENKINS_URL=${JENKINS_URL}" \
|
||||
-e JENKINS_VITEST_FULL=1 \
|
||||
-w /workspace/frontend \
|
||||
"$FE_CONTAINER" \
|
||||
bash -lc '
|
||||
set -euxo pipefail
|
||||
test -f package.json
|
||||
node --version
|
||||
corepack enable
|
||||
pnpm --version
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run i18n:compile
|
||||
pnpm run test:unit -- --run
|
||||
'
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,9 +196,9 @@ pipeline {
|
||||
steps {
|
||||
sh '''
|
||||
set -euxo pipefail
|
||||
echo "== E2E: levanta docker-compose.yml (host) y pnpm test:e2e =="
|
||||
if [ ! -f "$WORKSPACE/frontend/package.json" ] || [ ! -f "$WORKSPACE/docker-compose.yml" ]; then
|
||||
echo "ERROR: faltan archivos de repo (frontend o compose)"
|
||||
echo "== E2E: compose up con ci override + pnpm test:e2e =="
|
||||
if [ ! -f "$WORKSPACE/frontend/package.json" ] || [ ! -f "$WORKSPACE/docker-compose.yml" ] || [ ! -f "$WORKSPACE/docker-compose.ci.yml" ]; then
|
||||
echo "ERROR: faltan archivos de repo (frontend, compose o ci.override)"
|
||||
exit 1
|
||||
fi
|
||||
export E2E_COMPOSE_PROJECT="anexo76-e2e-${BUILD_NUMBER}"
|
||||
@@ -194,6 +206,7 @@ pipeline {
|
||||
cd "$WORKSPACE"
|
||||
: "${BUILD_NUMBER:=0}"
|
||||
E2E_ENV_FILE="${WORKSPACE}/.env.e2e.generated"
|
||||
|
||||
# Puertos (dash/sh en Jenkins, sin depender de $RANDOM): rango ~20000–60k
|
||||
T=$(date +%s 2>/dev/null || echo 0)
|
||||
r1=$((T % 20000))
|
||||
@@ -210,10 +223,14 @@ pipeline {
|
||||
echo "E2E: Keycloak (host) en puertos de .env e2e:" && cat "$E2E_ENV_FILE"
|
||||
|
||||
e2e_compose() {
|
||||
docker compose --env-file "$E2E_ENV_FILE" -f "$WORKSPACE/docker-compose.yml" "$@"
|
||||
docker compose \
|
||||
--env-file "$E2E_ENV_FILE" \
|
||||
-f "$WORKSPACE/docker-compose.yml" \
|
||||
-f "$WORKSPACE/docker-compose.ci.yml" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
# Nombres fijos en docker-compose: otro run / otro COMPOSE_PROJECT deja contenedors → "name already in use"
|
||||
# Nombres fijos en docker-compose: otro run / otro COMPOSE_PROJECT deja contenedores → "name already in use"
|
||||
e2e_force_rm_stale_containers() {
|
||||
echo "E2E: eliminando contenedors anteriores (mismos container_name) si siguen en el nodo"
|
||||
for c in \
|
||||
@@ -236,9 +253,7 @@ pipeline {
|
||||
e2e_compose down --remove-orphans 2>/dev/null || true
|
||||
e2e_force_rm_stale_containers
|
||||
}
|
||||
e2e_force_rm_stale_containers
|
||||
trap compose_down EXIT
|
||||
compose_down
|
||||
|
||||
e2e_compose_fail_logs() {
|
||||
echo "== E2E: fallo al levantar stack; diagnóstico =="
|
||||
echo "--- .env.e2e.generated ---"
|
||||
@@ -246,72 +261,54 @@ pipeline {
|
||||
export COMPOSE_PROJECT_NAME="${E2E_COMPOSE_PROJECT}"
|
||||
echo "--- docker compose ps -a ---"
|
||||
e2e_compose ps -a 2>&1 || true
|
||||
echo "--- logs: anexo76-postgres-a76 (Postgres app) ---"
|
||||
docker logs --tail 250 anexo76-postgres-a76 2>&1 || true
|
||||
echo "--- logs: anexo76-postgres-keycloak (Postgres de Keycloak) ---"
|
||||
docker logs --tail 400 anexo76-postgres-keycloak 2>&1 || true
|
||||
echo "--- logs: anexo76-keycloak (Keycloak) ---"
|
||||
docker logs --tail 250 anexo76-keycloak 2>&1 || true
|
||||
echo "--- logs: anexo76-backend (FastAPI / migraciones) ---"
|
||||
docker logs --tail 400 anexo76-backend 2>&1 || true
|
||||
echo "--- logs: worker (celery) ---"
|
||||
docker logs --tail 200 worker 2>&1 || true
|
||||
echo "--- logs: celery_beat ---"
|
||||
docker logs --tail 200 celery_beat 2>&1 || true
|
||||
for svc in anexo76-postgres-a76 anexo76-postgres-keycloak anexo76-keycloak anexo76-backend anexo76-frontend worker celery_beat anexo76-minio valkey; do
|
||||
echo "--- logs: $svc ---"
|
||||
docker logs --tail 300 "$svc" 2>&1 || true
|
||||
done
|
||||
}
|
||||
# Postgresql (app) + Alembic antes del resto: evita 10+ min "Waiting" mientras el lifespan
|
||||
# migra y frontend/celery esperan service_healthy. El start de backend hará "upgrade" idempotente.
|
||||
echo "== E2E: Postgres (app) + migraciones Antes del resto =="
|
||||
if ! e2e_compose build backend; then
|
||||
e2e_compose_fail_logs
|
||||
exit 1
|
||||
fi
|
||||
if ! e2e_compose up -d postgres-a76; then
|
||||
e2e_compose_fail_logs
|
||||
exit 1
|
||||
fi
|
||||
_pg=0
|
||||
while [ "$_pg" -lt 90 ]; do
|
||||
if e2e_compose exec -T postgres-a76 pg_isready -U postgres -d anexo76_core 2>/dev/null; then
|
||||
echo "E2E: Postgres (anexo76_core) acepta conexiones"
|
||||
break
|
||||
fi
|
||||
_pg=$((_pg + 1))
|
||||
sleep 2
|
||||
done
|
||||
if ! e2e_compose exec -T postgres-a76 pg_isready -U postgres -d anexo76_core 2>/dev/null; then
|
||||
echo "ERROR: timeout esperando a postgres-a76"
|
||||
e2e_compose_fail_logs
|
||||
exit 1
|
||||
fi
|
||||
# e2e_alembic_upgrade.py: carga /app/alembic.ini en la API; la CLI a veces queda con config vacío bajo compose (No 'script_location')
|
||||
if ! e2e_compose run --rm --no-deps --entrypoint /usr/local/bin/python3 backend /app/e2e_alembic_upgrade.py; then
|
||||
echo "ERROR: falló alembic upgrade head (job previo al stack completo)"
|
||||
e2e_compose_fail_logs
|
||||
exit 1
|
||||
fi
|
||||
echo "== E2E: stack completo =="
|
||||
if ! e2e_compose up -d --build; then
|
||||
|
||||
e2e_force_rm_stale_containers
|
||||
trap compose_down EXIT
|
||||
compose_down
|
||||
|
||||
# El lifespan del backend ejecuta Alembic al arrancar. El healthcheck del
|
||||
# backend tiene start_period=600s precisamente para permitirlo, y los
|
||||
# servicios dependientes (frontend, celery) esperan service_healthy.
|
||||
echo "== E2E: levantar stack completo =="
|
||||
if ! e2e_compose up -d; then
|
||||
e2e_compose_fail_logs
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bash "$WORKSPACE/scripts/wait-for-jenkins-stack.sh"
|
||||
# --network host: en Linux el contenedor de tests ve localhost:5173/8000 publicados por compose
|
||||
docker run --rm --network host \
|
||||
|
||||
# Playwright con docker cp (no -v): el workspace del agente Jenkins puede
|
||||
# no ser visible al docker daemon. `--network host` conecta al host del
|
||||
# daemon donde compose publica 5173/8000.
|
||||
PW_CONTAINER="anexo76-e2e-pw-${BUILD_NUMBER}"
|
||||
docker rm -f "$PW_CONTAINER" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$PW_CONTAINER" --network host \
|
||||
-e CI=true \
|
||||
-e "JENKINS_URL=${JENKINS_URL}" \
|
||||
-e PLAYWRIGHT_TEST_BASE_URL=http://127.0.0.1:5173 \
|
||||
-v "$WORKSPACE:/workspace" \
|
||||
-w /workspace/frontend \
|
||||
"$PLAYWRIGHT_TEST_IMAGE" \
|
||||
bash -lc '
|
||||
set -euxo pipefail
|
||||
test -f package.json
|
||||
corepack enable
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run i18n:compile
|
||||
pnpm run test:e2e
|
||||
'
|
||||
sleep infinity
|
||||
docker exec "$PW_CONTAINER" mkdir -p /workspace
|
||||
docker cp "$WORKSPACE/." "$PW_CONTAINER:/workspace"
|
||||
|
||||
if ! docker exec -w /workspace/frontend "$PW_CONTAINER" bash -lc '
|
||||
set -euxo pipefail
|
||||
test -f package.json
|
||||
corepack enable
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run i18n:compile
|
||||
pnpm run test:e2e
|
||||
'; then
|
||||
docker rm -f "$PW_CONTAINER" >/dev/null 2>&1 || true
|
||||
e2e_compose_fail_logs
|
||||
exit 1
|
||||
fi
|
||||
docker rm -f "$PW_CONTAINER" >/dev/null 2>&1 || true
|
||||
'''
|
||||
}
|
||||
}
|
||||
@@ -353,6 +350,9 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
// Build independiente con APP_VERSION (el tag release se materializa como build-arg
|
||||
// y como tag de imagen). No reutilizamos anexo76-backend:latest: aquí queda el tag
|
||||
// remoto ${REGISTRY}/${IMAGE_NAMESPACE}/backend:${APP_VERSION}.
|
||||
stage('Build + push backend') {
|
||||
steps {
|
||||
script {
|
||||
@@ -375,6 +375,8 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
// Frontend Dockerfile.prod ≠ Dockerfile (dev). No reutilizamos anexo76-frontend:latest
|
||||
// del stage CI; el build de producción necesita VITE_* fijos al dominio dev.
|
||||
stage('Build + push frontend') {
|
||||
steps {
|
||||
script {
|
||||
@@ -412,7 +414,7 @@ pipeline {
|
||||
git remote set-url origin "https://${GIT_USERNAME}:${GIT_PASSWORD}@git.aduanasoft.com/ADUANASOFT/anexo76.git"
|
||||
git push origin "v${APP_VERSION}"
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
"""
|
||||
E2E/Jenkins: aplica migraciones sin la CLI de Alembic.
|
||||
|
||||
docker compose run … alembic --config=… a veces deja un Config vacío (No 'script_location');
|
||||
aquí se carga explícitamente /app/alembic.ini y se llama a command.upgrade().
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = Path("/app")
|
||||
if not root.is_dir():
|
||||
print("ERROR: /app no es un directorio (volumen backend).", file=sys.stderr)
|
||||
return 1
|
||||
os.chdir(root)
|
||||
sys.path.insert(0, str(root))
|
||||
|
||||
ini = root / "alembic.ini"
|
||||
if not ini.is_file():
|
||||
print(
|
||||
f"ERROR: {ini} no existe. Comprueba el bind ./backend:./app en el agente.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
from alembic.config import Config
|
||||
from alembic import command
|
||||
|
||||
command.upgrade(Config(str(ini)), "head")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
32
docker-compose.ci.yml
Normal file
32
docker-compose.ci.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
# Override para CI/Jenkins (E2E + pipeline).
|
||||
#
|
||||
# Problema que resuelve: en Jenkins los bind mounts `./backend:/app` y
|
||||
# `./frontend:/app` del compose base NO funcionan cuando el agente corre en un
|
||||
# contenedor y el docker daemon del host no ve `$WORKSPACE`. El daemon monta
|
||||
# un directorio vacío sobre `/app` y la app arranca sin código.
|
||||
#
|
||||
# Estrategia: con `!override` se reemplaza por completo la lista de volumes de
|
||||
# cada servicio, dejando solo los volúmenes nombrados (cache, uploads,
|
||||
# node_modules). El código se usa desde la imagen construida por
|
||||
# `docker compose build`, que sí se transfiere por el daemon API.
|
||||
#
|
||||
# Requiere Docker Compose v2.24+ (tag `!override`).
|
||||
services:
|
||||
backend:
|
||||
volumes: !override
|
||||
- backend_cache:/app/__pycache__
|
||||
- backend_uploads:/app/uploads
|
||||
|
||||
frontend:
|
||||
volumes: !override
|
||||
- frontend_node_modules:/app/node_modules
|
||||
|
||||
celery_worker:
|
||||
volumes: !override
|
||||
- backend_cache:/app/__pycache__
|
||||
- backend_uploads:/app/uploads
|
||||
|
||||
celery_beat:
|
||||
volumes: !override
|
||||
- backend_cache:/app/__pycache__
|
||||
- backend_uploads:/app/uploads
|
||||
Reference in New Issue
Block a user