From ad15097c98c51cc13dd923a323f433db76ffd04d Mon Sep 17 00:00:00 2001 From: Nachou-u Date: Wed, 9 Sep 2026 12:19:29 -0300 Subject: [PATCH] MEJORA: usar imagenes pre-construidas del registry de Gitea en lugar de build local para evitar bind-mount en Portainer --- Dockerfile | 4 +- docker-compose.yml | 13 +--- requirements.txt | 2 +- scripts/analyze_django_endpoints.py | 110 ++++++++++++++++++++++++++++ scripts/build_and_push.sh | 50 +++++++++++++ 5 files changed, 166 insertions(+), 13 deletions(-) create mode 100644 scripts/analyze_django_endpoints.py create mode 100755 scripts/build_and_push.sh diff --git a/Dockerfile b/Dockerfile index 14d7e27..ac0d8ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ FROM python:3.12-slim # Establecer variables de entorno -ENV PYTHONDONTWRITEBYTECODE 1 -ENV PYTHONUNBUFFERED 1 +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 # Establecer directorio de trabajo WORKDIR /app diff --git a/docker-compose.yml b/docker-compose.yml index 494deba..5ffb682 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,10 @@ -version: '3.8' - services: web: - build: . + image: git.controlmc.click/ginevra_oa/erp-efintel-2026/web:latest command: gunicorn config.asgi:application -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --workers 3 restart: unless-stopped environment: REDIS_URL: redis://redis:6379/0 - # Django necesita escuchar en 0.0.0.0 (ya lo hace gunicorn) volumes: - erp_media:/app/media - erp_static:/app/static @@ -35,10 +32,9 @@ services: networks: - internal mem_limit: 512m - # Redis no necesita puertos públicos ni etiquetas Traefik celery: - build: . + image: git.controlmc.click/ginevra_oa/erp-efintel-2026/web:latest command: celery -A config worker -l info --concurrency=2 restart: unless-stopped environment: @@ -50,19 +46,16 @@ services: mem_limit: 512m tax-api: - build: ./tax_api + image: git.controlmc.click/ginevra_oa/erp-efintel-2026/tax-api:latest restart: unless-stopped networks: - internal mem_limit: 256m - # Si la API de impuestos necesita ser expuesta al público por Traefik, - # se deben agregar etiquetas similares a las del servicio 'web' con un ROUTER_NAME diferente. networks: controlmc_proxy: external: true internal: - # Red interna para que web, redis, celery y tax-api se comuniquen sin exponerse volumes: erp_media: diff --git a/requirements.txt b/requirements.txt index 843defa..6e55544 100644 --- a/requirements.txt +++ b/requirements.txt @@ -52,7 +52,7 @@ wheel==0.42.0 zope.interface==8.5 whitenoise==6.7.0 -django-storages==2.14.2 +django-storages==1.14.6 boto3==1.34.69 requests==2.31.0 uvicorn[standard] diff --git a/scripts/analyze_django_endpoints.py b/scripts/analyze_django_endpoints.py new file mode 100644 index 0000000..ee925b2 --- /dev/null +++ b/scripts/analyze_django_endpoints.py @@ -0,0 +1,110 @@ +import os +import sys +import django +import json + +# Set up Django environment +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") +django.setup() + +from django.test import Client +from django.contrib.auth import get_user_model +from django.urls import get_resolver, URLPattern, URLResolver + +def get_all_urls(urlpatterns, prefix=''): + urls = [] + for pattern in urlpatterns: + if isinstance(pattern, URLPattern): + # Try to build a valid URL by replacing common parameters + url = prefix + str(pattern.pattern) + url = url.replace('', '1') + url = url.replace('', '1') + url = url.replace('', '1') + url = url.replace('', 'approve') + url = url.replace('^', '').replace('$', '') + urls.append(url) + elif isinstance(pattern, URLResolver): + urls.extend(get_all_urls(pattern.url_patterns, prefix + str(pattern.pattern))) + return urls + +def run_analysis(): + User = get_user_model() + # Try to get an existing admin or any user, or create a temporary one + user = User.objects.filter(is_superuser=True).first() or User.objects.first() + created_user = False + if not user: + user = User.objects.create_superuser('test_admin', 'admin@example.com', 'password123') + created_user = True + + client = Client() + client.force_login(user) + + resolver = get_resolver() + urls = get_all_urls(resolver.url_patterns) + + # Filter out admin, media, and tax_api endpoints + urls = [u for u in urls if not u.startswith('admin/') and not u.startswith('media/') and 'tax_api' not in u] + + # Prepend / to urls if needed + urls = ['/' + u if not u.startswith('/') else u for u in urls] + + results = [] + + for url in urls: + # Ignore endpoints that aren't strictly GET or testable by simple GET + if url == '/logout/': + continue + + try: + response = client.get(url) + status_code = response.status_code + content_type = response.headers.get('Content-Type', '') + + snippet = "" + if 'application/json' in content_type: + try: + data = response.json() + snippet = json.dumps(data)[:150] + ("..." if len(json.dumps(data)) > 150 else "") + except Exception: + snippet = response.content.decode('utf-8', errors='ignore')[:150] + else: + snippet = response.content.decode('utf-8', errors='ignore')[:150].replace('\n', ' ').strip() + if len(response.content) > 150: + snippet += "..." + + results.append({ + 'url': url, + 'status_code': status_code, + 'snippet': snippet, + 'content_type': content_type + }) + except Exception as e: + results.append({ + 'url': url, + 'status_code': 'Error', + 'snippet': str(e), + 'content_type': 'N/A' + }) + + # Cleanup user if we created it + if created_user: + user.delete() + + return results + +if __name__ == '__main__': + print("Starting endpoints analysis...") + results = run_analysis() + + artifact_path = "/home/cheerry/.gemini/antigravity/brain/5fe9c8b0-cf58-4038-b407-4851abf7278b/django_endpoints.md" + + with open(artifact_path, "w", encoding="utf-8") as f: + f.write("# Django Endpoints Analysis\n\n") + f.write("| Endpoint | Status Code | Content Type | Snippet |\n") + f.write("|----------|-------------|--------------|---------|\n") + for res in results: + # Escape pipe symbols for markdown tables + snippet = res['snippet'].replace('|', '\\|').replace('\n', ' ') + f.write(f"| `{res['url']}` | {res['status_code']} | {res['content_type']} | `{snippet}` |\n") + + print(f"Analysis successfully saved to {artifact_path}") diff --git a/scripts/build_and_push.sh b/scripts/build_and_push.sh new file mode 100755 index 0000000..778117d --- /dev/null +++ b/scripts/build_and_push.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# ============================================ +# Script para construir y subir imágenes Docker +# al Container Registry de Gitea (ControlMC) +# ============================================ + +set -e + +REGISTRY="git.controlmc.click" +OWNER="ginevra_oa" +REPO="erp-efintel-2026" +TAG="latest" + +WEB_IMAGE="${REGISTRY}/${OWNER}/${REPO}/web:${TAG}" +TAX_IMAGE="${REGISTRY}/${OWNER}/${REPO}/tax-api:${TAG}" + +echo "============================================" +echo " ERP Efintel - Build & Push al Registry" +echo "============================================" + +# Paso 1: Login en el registry de Gitea +echo "" +echo "[1/4] Iniciando sesión en el registry de Gitea..." +echo " (Usa tu usuario de Gitea y un Access Token como contraseña)" +docker login ${REGISTRY} + +# Paso 2: Construir imagen web (Django) +echo "" +echo "[2/4] Construyendo imagen web (Django + Gunicorn)..." +docker build -t ${WEB_IMAGE} . + +# Paso 3: Construir imagen tax-api (FastAPI) +echo "" +echo "[3/4] Construyendo imagen tax-api (FastAPI)..." +docker build -t ${TAX_IMAGE} ./tax_api + +# Paso 4: Subir ambas imágenes al registry +echo "" +echo "[4/4] Subiendo imágenes al registry de Gitea..." +docker push ${WEB_IMAGE} +docker push ${TAX_IMAGE} + +echo "" +echo "============================================" +echo " ¡Listo! Imágenes subidas exitosamente." +echo " web: ${WEB_IMAGE}" +echo " tax-api: ${TAX_IMAGE}" +echo "============================================" +echo "" +echo "Ahora ve a Portainer y haz 'Pull and redeploy'"