diff --git a/Jenkinsfile b/Jenkinsfile index 6512be28..321fead5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -57,7 +57,6 @@ pipeline { 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}" @@ -80,13 +79,19 @@ pipeline { -e POSTGRES_PASSWORD="$DB_PASS" \ postgres:16-alpine - # Espera a que Postgres acepte conexiones + # 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; @@ -165,33 +170,47 @@ pipeline { 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 + script { + retry(3) { + sh ''' + set -euo pipefail + export DOCKER_BUILDKIT=1 + docker build \ + --build-arg APP_VERSION="${APP_VERSION}" \ + -t "${REGISTRY}/${IMAGE_NAMESPACE}/backend:${APP_VERSION}" \ + -t "${REGISTRY}/${IMAGE_NAMESPACE}/backend:latest" \ + -f ./backend/Dockerfile \ + ./backend - docker push "${REGISTRY}/${IMAGE_NAMESPACE}/backend:latest" - ''' + docker push "${REGISTRY}/${IMAGE_NAMESPACE}/backend:${APP_VERSION}" + 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 + script { + retry(3) { + sh ''' + set -euo pipefail + export DOCKER_BUILDKIT=1 + 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:${APP_VERSION}" \ + -t "${REGISTRY}/${IMAGE_NAMESPACE}/frontend:latest" \ + -f ./frontend/Dockerfile.prod \ + ./frontend - docker push "${REGISTRY}/${IMAGE_NAMESPACE}/frontend:latest" - ''' + docker push "${REGISTRY}/${IMAGE_NAMESPACE}/frontend:${APP_VERSION}" + docker push "${REGISTRY}/${IMAGE_NAMESPACE}/frontend:latest" + ''' + } + } } } @@ -229,13 +248,20 @@ pipeline { ) ]) { script { + def devHost = env.DEV_SERVER_HOST?.trim() + def knownHostsFile = "${env.WORKSPACE}/.jenkins-anexo76-known_hosts" + sh """ + set -euo pipefail + ssh-keyscan -T 15 -H '${devHost}' > '${knownHostsFile}' + """ def remote = [ name: 'dev-server', - host: env.DEV_SERVER_HOST, + host: devHost, user: env.DEV_SERVER_USER ?: env.DEV_SERVER_SSH_USER, identityFile: env.DEV_SERVER_KEY, passphrase: env.DEV_SERVER_KEY_PASSPHRASE, - allowAnyHosts: true + allowAnyHosts: false, + knownHosts: knownHostsFile ] sshCommand remote: remote, command: ''' set -euo pipefail @@ -243,6 +269,7 @@ pipeline { # 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 + # Solo imágenes colgantes (sin tag); no borra imágenes en uso por otros contenedores. docker image prune -f ''' } diff --git a/backend/Dockerfile b/backend/Dockerfile index 6f6a9c20..84d0d41d 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -45,6 +45,10 @@ RUN pip install --no-cache-dir -r requirements.txt # Copiar código COPY . . +# Jenkins / CI pasan --build-arg APP_VERSION=…; debe quedar en ENV para runtime (API /version, OpenAPI, etc.) +ARG APP_VERSION=dev-local +ENV APP_VERSION=${APP_VERSION} + # Exponer puerto EXPOSE 8000 diff --git a/backend/core/config.py b/backend/core/config.py index e356a397..7ad763b5 100644 --- a/backend/core/config.py +++ b/backend/core/config.py @@ -2,7 +2,6 @@ Configuración centralizada de la aplicación usando Pydantic Settings """ -import os from typing import List, Literal from pydantic import field_validator @@ -14,9 +13,8 @@ class Settings(BaseSettings): # Application APP_NAME: str = "Anexo76" - # La versión se obtiene de la variable de entorno APP_VERSION que se pasa desde Docker - # Si no existe, usa un valor por defecto de desarrollo - APP_VERSION: str = os.getenv("APP_VERSION", "dev-local") + # Sobreescribible con APP_VERSION (Dockerfile/Jenkins: build-arg + ENV) o entorno en runtime + APP_VERSION: str = "dev-local" DEBUG: bool = True ENVIRONMENT: str = "development"