From 039349b5b1ed6a4f7bcc8a1f746486a5df74b154 Mon Sep 17 00:00:00 2001 From: minguezsanzjuanjose Date: Tue, 14 Apr 2026 21:30:19 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20integraci=C3=B3n=20final=20de=20Jenkins?= =?UTF-8?q?=20con=20inyecci=C3=B3n=20de=20.env=20segura?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 10 --- .env.example | 10 --- .gitignore | 4 + .../migrations/0002_alter_log_options.py | 17 ++++ core/.env.example | 3 + core/settings.py | 80 +++++++++++------- db.sqlite3 | Bin 16384 -> 155648 bytes deployments/.env.example | 34 ++++++++ deployments/Dockerfile | 18 ++-- deployments/Jenkinsfile | 48 +++++++---- deployments/docker-compose.yml | 36 ++++---- deployments/entrypoint.sh | 15 ++++ 12 files changed, 186 insertions(+), 89 deletions(-) delete mode 100644 .env delete mode 100644 .env.example create mode 100644 apps/backend_admin/migrations/0002_alter_log_options.py create mode 100644 core/.env.example create mode 100644 deployments/.env.example create mode 100644 deployments/entrypoint.sh diff --git a/.env b/.env deleted file mode 100644 index 5be865a..0000000 --- a/.env +++ /dev/null @@ -1,10 +0,0 @@ -# Seguridad -DEBUG=True -SECRET_KEY=una-clave-muy-secreta-y-larga-123456 - -# Base de Datos (Conectando al PostgreSQL que instalamos) -DB_NAME=django_test -DB_USER=django_user -DB_PASSWORD=django_password -DB_HOST=db -DB_PORT=5432 \ No newline at end of file diff --git a/.env.example b/.env.example deleted file mode 100644 index f8f4030..0000000 --- a/.env.example +++ /dev/null @@ -1,10 +0,0 @@ -# Seguridad -DEBUG=True -SECRET_KEY=una-clave-muy-secreta-y-larga-123456 - -# Base de Datos (Conectando al PostgreSQL que instalamos) -DB_NAME=gitea -DB_USER=gitea -DB_PASSWORD=gitea -DB_HOST=gitea-db -DB_PORT=5432 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0bba40d..066dafe 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ postgres_data/ local_postgres_data/ *.pyc apps/backend_admin/migrations/0001_initial.py +# Bloquear todos los .env en cualquier carpeta +.env +**/core/.env +**/deployments/.env \ No newline at end of file diff --git a/apps/backend_admin/migrations/0002_alter_log_options.py b/apps/backend_admin/migrations/0002_alter_log_options.py new file mode 100644 index 0000000..ce7ca54 --- /dev/null +++ b/apps/backend_admin/migrations/0002_alter_log_options.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.3 on 2026-04-14 19:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('backend_admin', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='log', + options={'managed': False}, + ), + ] diff --git a/core/.env.example b/core/.env.example new file mode 100644 index 0000000..be7f52a --- /dev/null +++ b/core/.env.example @@ -0,0 +1,3 @@ +# core/.env +APP_CUSTOM_SETTING="Este es un valor privado de la app" +EXTERNAL_SERVICE_API_KEY="sk_test_12345" \ No newline at end of file diff --git a/core/settings.py b/core/settings.py index 13c8d2d..ae2e211 100644 --- a/core/settings.py +++ b/core/settings.py @@ -3,35 +3,30 @@ import os from dotenv import load_dotenv from datetime import timedelta -# Cargar variables desde el archivo .env -load_dotenv() +# 1. RUTA DEL SETTINGS Y CARGA DEL .ENV +# Obtenemos la ruta de la carpeta donde está este archivo (core/) +CURRENT_DIR = Path(__file__).resolve().parent +BASE_DIR = CURRENT_DIR.parent -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent +# Cargamos el .env específico de esta carpeta (core/.env) +load_dotenv(dotenv_path=CURRENT_DIR / '.env') # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.getenv('SECRET_KEY', 'django-insecure-change-me-for-production') +SECRET_KEY = os.getenv('SECRET_KEY', 'django-insecure-default-key-change-it') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = os.getenv('DEBUG', 'True').lower() == 'true' - -# Constants from .env -DB_NAME = os.getenv('DB_NAME') -DB_USER = os.getenv('DB_USER') -DB_PASSWORD = os.getenv('DB_PASSWORD') -DB_HOST = os.getenv('DB_HOST') -DB_PORT = os.getenv('DB_PORT') +# Mejoramos el parseo de DEBUG para que no falle si viene como string +DEBUG = os.getenv('DEBUG', 'True').lower() in ('true', '1', 't') +# 2. ALLOWED HOSTS +# Limpiamos y centralizamos los hosts permitidos ALLOWED_HOSTS = [ 'v-encore-lab.com', 'dev.v-encore-lab.com', - '185.187.169.109', # Añade la IP aquí + '185.187.169.109', 'localhost', '127.0.0.1', - 'django_app_dev', - 'django_app_master', - 'rest_framework', - 'rest_framework.authtoken' + os.getenv('APP_CONTAINER_NAME', 'django_app_dev'), # Dinámico para Docker ] # Application definition @@ -42,12 +37,16 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + + # Plugins 'rest_framework', 'rest_framework.authtoken', + 'corsheaders', + + # Tus Apps (Asegúrate de que el path sea correcto) 'apps.promociones', 'apps.backend_admin', 'apps.common', - 'corsheaders', ] MIDDLEWARE = [ @@ -81,15 +80,16 @@ TEMPLATES = [ WSGI_APPLICATION = 'core.wsgi.application' -# Database +# 3. DATABASE +# Extraemos con fallback por si el .env falla DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', - 'NAME': DB_NAME, - 'USER': DB_USER, - 'PASSWORD': DB_PASSWORD, - 'HOST': DB_HOST, - 'PORT': DB_PORT, + 'NAME': os.getenv('DB_NAME', 'postgres'), + 'USER': os.getenv('DB_USER', 'postgres'), + 'PASSWORD': os.getenv('DB_PASSWORD', ''), + 'HOST': os.getenv('DB_HOST', 'localhost'), + 'PORT': os.getenv('DB_PORT', '5432'), } } @@ -99,7 +99,7 @@ TIME_ZONE = 'Europe/Madrid' USE_I18N = True USE_TZ = True -# Static files (CSS, JavaScript, Images) +# Static files STATIC_URL = '/static/' STATIC_ROOT = BASE_DIR / 'staticfiles' @@ -120,27 +120,43 @@ SIMPLE_JWT = { "REFRESH_TOKEN_LIFETIME": timedelta(days=1), } -# CORS -CORS_ALLOW_ALL_ORIGINS = True +# CORS & CSRF +CORS_ALLOW_ALL_ORIGINS = DEBUG # Solo permitir todo en modo DEBUG CSRF_TRUSTED_ORIGINS = [ - 'chrome-extension://amknoiejhlmhancpahfcfcfhllgkpbld', + 'https://v-encore-lab.com', 'http://localhost:8000', 'http://127.0.0.1:8000', ] -# Logging +# Logging simplificado LOGGING = { 'version': 1, 'disable_existing_loggers': False, + 'formatters': { + 'verbose': { + 'format': '{levelname} {asctime} {module} {message}', + 'style': '{', + }, + }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', + 'formatter': 'verbose', }, }, 'loggers': { '': { 'handlers': ['console'], - 'level': 'INFO', + 'level': os.getenv('LOG_LEVEL', 'INFO'), }, }, -} \ No newline at end of file +} + +# --- CONFIGURACIONES PERSONALIZADAS DE LA APP --- + +# Leemos la variable del .env (cargado previamente con load_dotenv) +# Ponemos un valor por defecto por si se nos olvida ponerlo en el .env +APP_CUSTOM_SETTING = os.getenv('APP_CUSTOM_SETTING', 'valor_por_defecto_seguro') + +# Ejemplo de otra variable de API +EXTERNAL_SERVICE_API_KEY = os.getenv('EXTERNAL_SERVICE_API_KEY', None) \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 index 933e1e8df4fc3fa595d10548dfb9355884a67a2f..e3b2ea187a2b04c9f8cc53b0cda5ad9532339f2c 100644 GIT binary patch literal 155648 zcmeI5TWlNIdB-^>C5n{Du`O%q^-YvySz@%3hcmn?-R#N|Ez1@u>n;jeFhg=Ajl`Qt z%DQaRR^Htvy}h(WfV63ompl}0gSKgqplwjJLD4ovl9!}FnxsJbvMGvff}%(slA=B5 zaL5@_lr4MR#Pa?L?-A#m?|kz=zjK{4XQlbsX|=4l*Yd@zTy{&Q(KRLtqqrOg>M)vQuP-Fhb)aHfCkE6^H4+0xPC5RoCh`v-5C8!X009sH0T2KI5C8!X009sHffGSM;H;*eo}kTk zx0ui7-3v;oEDB;^Snv&dy%S0)Ra6TpHJ@{*6?dVytt4go{BPrz&HNAe@A6;fKgWN9 zU*-?{rhyp_-2!H?xfB*=900@8p2!H?xfB*>mED3bBnuj=fyS$MoD8;N=Dv^io zK}Wl}r;SCf7xUYNjt+CTja_b+l%msN?jbqSYB`a~ub0}0dRL2C;?kRPZatq!<#S~v zS5B1o3W~kcVjgbO66ADN&Cx7fX7lx`byi(3%4KrTlFM<*++$10Wo12IROOPBGxymF zba#O611P1+`~P=L{5wBOT(~y~fB*=900@8p2!H?xfB*=900@ALI}TsR+Z~_mSn4>_{I%SU>9*SROd!AIB1&D9}M}0SI&`)@2|@!CWxARLKM6KK@cAHe zB(JZNIOwl)(5%?HUcnm{ebU($)8+oLlEPm3m5yLVGI@njzc>>1OMW4E>r9KOYnk1O zuq84XCi+$|v8EPFco>bv0X@$<$j_9 zTYnWBAZL3A4`i_>qqAevz&5O+_hlDUG+6_B#ai{9SZ!8xBMBe9; zx4OCQjIoE+T1`msO5wn(HsYgN*R{^YT1|}&>0eC?5)6iU`_Y0N@g z5z3k?QFo=uVq*$vKF!nwLSn${7tF*38A&u3M~otsZX5^*{;>c0DZ;ce_B?6?2}nLs z3I;iXuD=o8!|x>cgs|66<}EuvV&eak|0n)m_%HIW@e}+A|0(_+|7*|Zl!j7)00@8p z2!H?xfB*=900@8p2!Oz&2y|HnI=Q;dP_nULKX2)#NB6ajPJMUN)@kW>H6*mXXzAm^HnA7|VVF??@vSh~8Hf4bjeaay`6cVnA%=1xmjMU}o4 zbLx!6b=k)DA*vf9^!@*!{i=!oHveb*NBJZ_%6B;b)%oYnk2#CZm~+H=*6{)i zc+0Wi=C!4|R-oINSfN{V&=-(Z1Wh(C%wLWB;E0ukF8M|0Vl}?5`4s z_<#TifB*=900@8p2%J~~7Y18Q+~t1zeO1|^ufN9SoSS}klkZmsNLr7_o>npndC{z; zR<2n3wMu$wgZyu-we-prO+VjH(l7Se>HAkLGx~HZ^cm4QE#ZpVTL_D#LzQ9R>^6@IRkoEs*9OiaTAjsPn)i+UQ};4 zN$)5Bhi-)lyBD=E>gl~wu`%kQHbyCzE)$#Xfwr2IIu+M0kyMY@%2d>zB5F!%G4H!b z?1ueTO-(J{pr{u2z6%vwO;s)4psW^m^+jSkaNSDP)gr43YZ1NYiBs;7Mdt}d4U|cAido}#Z;52 z3i|yDIYvQRzM0*fJ!aN!&#rpO$p}*0X6^np3r#@9h8%sMHkyK(jV6IzUm`~vNGZ)! zL(PDSU>Aer*a9~sonk8Vr`wtoJ?bSZvAiBUrRmWVsI4YSk9>)oKcLo{GChG>Yx4BS zYvhapX^p3-Mm?6AQlWaxRdRs9HsEDq$*{(bA=IRj2seuvBxeL{Lw-%D(N2@8TavQ@ z#8MM!wA3WlEPKhJ09*fcD$r;~<<)HH`~Sg?w@v)_$=m17aX5<{HEi;ks|_VwAOHd&00JNY0w4eaAOHdVSc9i6Tn}g1vl^&G=+9Kw z92V{(SKXEB@3L@1T;opERhNak&K=%*>Z`Ly^{_h|wvyauEnGiW zw|CTCi84C8!dtj*PTv{2%zQWO1zmcAmTWtNT z1y*A!zyEK~#P6N-E)M-500JNY0w4eaAOHd&00JNY0wC}V5;(AM4%5NQCY#IQu-V2O zh2&OxO-z(FWYHhEN`5a1`CKjRXX6`p#*$0-$K#9ZD^fH$;oI7~I zH&+5TqtcDN^unE6>#Moxsp-wF#mU0V^5z|3Vlw|SJMCZpy9p%nYg$4hg3lM0!u0(= z$N$Jg{^0`xAOHd&00JNY0w4eaAOHd&00JQJd=Y3fyDGm2fWH4XoqoOwhthxm2!H?x zfB*=900@8p2!H?xfB*>86TtI-Jr7)g00@8p2!H?xfB*=900@8p2!O!zNPwRI$NT^1 zv1U;w5C8!X009sH0T2KI5C8!X009ut3E=&|&H~>+00ck)1V8`;KmY_l00ck)1VG?< zC4lGu=e1^0E)W0#5C8!X009sH0T2KI5C8!X&I&Kg-|YBm66T zn3woret_@cFA|6NfB*=900@8p2!H?xfB*=900@An*bH%90K0T2KI z5C8!X009sH0T2KI5CDO9PXNFF@7*(m2M_=O5C8!X009sH0T2KI5C8!Xc-9Hv`Ttq3 zA?O1E5C8!X009sH0T2KI5C8!X0D*T;0MGyLo*_Jd00@8p2!H?xfB*=900@8p2!Oz| zPQcNcGM(l=YjXbB`3KHRj#t~pTLkkrI_BFyZU3IR^VBa{|G4$D_F?X`+?MH!k5!g~ zp|e)kYhmsns^-$luDo5|NURt0+l53yDQ4ADNzLamKh-0c95tSXvjxHLW&8C!_Br(zS46?gB^hK4#c zdhT`4#2%Kd*FD%WO-aJtLtni=K}ybXn6ZKYHx6A>Sfy`qv1C3Q^|gY!p8J|cgk z#EKae#)znCBI%WLR@WAhRC>51mX#Ji?34ULN=PNPbLzcDfi6wpVr*)5u?gA+oz0y- zTJ=lr-fGrf&w~}G)ipN8z4`vKoXjY?q{bF$b|_S>erPjjR`*m1SF4s2uGgX;|)w6S^DMi0H3*>L5P z1Id6SHuTrV+!r3va~tiCbGOEh+~|WMWpSjrA8OB@gU%PMu30iN`5OveX-)cU8V>sX zVZYDU*c@NfigajzKgvO)-ijSn$-WVqWVDIdt*&sGdo!qaiAt)Dy~LCpifwPwJq{T? zQ75U%d%BJJP@NpZy-wkn9fow4k3Y>0^VEDDgw9)CDYEj2H?+UTW?btEa@eoPX|G}0 zZZy4kRFiBpJmj;nZ8l~w_9f53Xs6Y+PL`vxF8xqTYgpEVd?|k_ELP^vL&oQt2tQuf>!-~#WPCPO7c=w>)t$oxqd7fB^Oh)D&vLvio z=knQPQE@Mg&5hq0n`32RX`Z{MT3tg!T%1WM-OH$DB~eoDZ7aEy(r{_66V;G7NO8PG za(W(|;;gQ|KJJY>%x9sP&*oF4&Xkhj>!~_lhJ>eC!k22rUsFgGCe)ltgt#ZjvzVTg zR!XU&TA&Ku3z3xtJw;BH)%$t(jhUJ0$XJZ7hsPGD7u;UmqE<0vIH}e~1*^k++SF7f(>FPtyq<+o)-SD1>OfC@qKaq~^zc=*)=Z6=w7WifMjuOV4O*C}ZSA7$UC#rs3Uv1Ii zZK#!s>gLHF(m~nptz{kcO;?bOA|Kh)+HzVgCo=hU^2k~JcCEe`dcqT_XU`eUw!8>z znx+q&jVm!)?jtf5m293)9O-nC%qP8Wwvv;=WZ)%xX~c1nE_F)f>KIEm*v4-~#_zZX z2XD{M#1czm(^C_JH8YQUX3mglYM%M=c$lYBfh$VhReRjHd2>L8_IF#L{0J$g6T$mvo_$?282UfZIvcgw+1ez{MdM`%LL*mjsIdyapi zj#rRNrJa1y*jU7nzBD1tAtRSq-&b=+`jsA}mJ+3HGH+FQCiD4>aj8OM8|H~7%Y*7I z9%4yMl&q{~^cF4peRU$Kkg~kC*6VH*K~mUSp7f>0B(|*FBn#c9k80$mYif~{{UO3U z2lIVa*WM8KfM=6g!z_PjRZ*Qe6M|PxNtbvucjc0<~SElt188l5(KY;EGm)`bGKC`RgX$ni*}+v!hR9`ge7z?5(r^z{_rdPKacx&8;hmESY!Z_1d(ky~bi~t!~k7c-2LxPUCp3@i0=> zkUV;!T5UZKUkcX-)?@qCicEKQjANo!JP*$QKlUaBGJpUGfB*=900@8p2!H?xfB*=9 zz=H|We^MkAOHd&00JNY0w4eaAOHd&00NIq0MGxAjSLw;00ck)1V8`;KmY_l z00ck)1VG@#5y12RiK{XQ1_2NN0T2KI5C8!X009sH0T2Lz$0mU1|Hnp#3?KjkAOHd& z00JNY0w4eaAOHd&aN-Ey`TxXK83cm>2!H?xfB*=900@8p2!H?xfWTuD!1MoOBSQud z009sH0T2KI5C8!X009sH0T4KG1n~TS;;IaSK>!3m00ck)1V8`;KmY_l00cnbu?aXx z4m&?=;@{!F$$y#uEB=%Gukr={gZwy�LaG00ck)1V8`;KmY_l00ck)1m24T&e<)d zfzG6y+EQ}qgq+T*xlDfDWwV&Nd*to%MmbLsSWqbDclXY=kpx$L!g;I3)Ynatv-y;o z&ncza*Unap>GH*rQYw-7lJ@gri^bI6m&)hL#Bq7App*>XJ1iDcR~PlaT~dk;Gr3}S zTQBCf3ujM}OQx!z6tgPxdWN%@T$ii5iO1p|ef~F{elJ!g$_xS^00JNY0w4eaAOHd& z00JNY0`DCHIRF3NscV!J1V8`;KmY_l00ck)1V8`;K;XSg0MGyL-MUA~K>!3m00ck) z1V8`;KmY_l00iDU1nBvHj{ky*{KE$XKmY_l00ck)1V8`;KmY_l00cnb86$9$v%EHD z6T)dl6urrm7)}RM{&YxDf??m9B8!p~N(BUOT2O>=z%L47U|8@CdnLCwJnEB1rNBtY z7xqg*`u?BeziuM`@Bsl3009sH0T2KI5C8!X009sH0T4Jr1cuD79eZb>^8Ei36aSMF zR6s}t0T2KI5C8!X009sH0T2KI5C8!XI5q*Z*=D2X|Jy9zGVx-^@7gW4iMBUe-f2mb z2z)>Q1VG^A5;$mWbC}v(ZKhZHJN;c_W9;vqyxyy(dq>^gm)yN_s;u76lZ$e3Tlw(o zy{Y_mu1wQDEDA#H&$1|`>-i-4>nlp}`rVoN1;a@yQ5Y4aQPDRN3i%`{+$<&R6@mfr zpw;?Q5aRWX2tL0kc@GJpDIsrI^uBej)yas+AN6+j=t$_FkIBhdg%rM*QR*dl3UaBm zlP^+QS|njqRMv`0X@kZod$%`|lPPsZy*;(KKjn?7Q>CfgoIf=_6`0y8tSpV+4v!Ec zAt{zK$xK*KR&HdbHuGvKyObsW?aSjNLw3nWGej3sQfxEgpP7hAGxIyDynIt2w!5+Y zlo(xz(4YS4@!R*(lUwA5Q#;Z9ly_z!v$47v-HmRp3(?7?t(nRAPHZ!?5uKdcC-yt> zg^kVV9u>8;zq)dJOLIpvdCrCGEeX)flb#)?>C8gY2-JIKsF0882<(r#P@%BbkjPA!KXGy-r zU3!o7N-UGggbS;*IyNKf+U!VhV`_f(?v3pm`^!;pWML`0H@|T^Gc_?2Qn#13@7$2@ zPYAo&`*(6XDOzaSD)h=H5v8(R-ewd^+Z7JN>CeIjX>4b76I zgh{jXg+jrDms?o>=y~NA^^WQdiX-08h%n-PNS9TL#XQqEliXBNWp{qfP5(%eJ1v(L zw_0-N@@4n!`I(rT6h)OY>b{)JD7_!{AGd##!;(^P#21jfqzw8QO49SSl(69S2M%7g zJgfQxqe5^b5C}@)hVjW;NeK&~K=`e5=4X6ZAl-RC)?`?S?nk})uyA`bMmovr!j?b2 z92LkAAw(CVJ2T5SNrzeAiHmV>bbligoxGhrYFG#h3%==l_r~t+F5R5JwZ1krGwaK4 zOy61$B{p;W`%`o4n>We}h3Ir-@&A8V*o|$n(lrhX`_cVHeOSmwr8pTX;>+>f_%b7~ zznYEsR+pDD@#V-~Or*mI@sy2-M+^(3Sh4jR`-(WXyCzD$`S^yUOy3NS%?EZz!&CYBwbK5?STUVWE|yoyd-18zT5k5I&@4%(qor$XmSj$j7@FmrI{Ulot>ra z{k84c;8^}xm;9}+hh(K(AJ&_ qHp3=1cghL6Y|ZBe*ev$2xmO5}ilLF9KNKXZ!n)FEDWO0}2>l-_t~e+F delta 83 zcmZoTz}e8iI6+#Fg@J(qgkgYbqK>gJ3xi%+CofQlnNNm+?>FB{KADY;t9ZA|_%S|Z e+{`GzEWp9U|B!+I1OLO#f(BRlH$T*u7XSc-d=lgU diff --git a/deployments/.env.example b/deployments/.env.example new file mode 100644 index 0000000..a71f24d --- /dev/null +++ b/deployments/.env.example @@ -0,0 +1,34 @@ +# ================================================================= +# 🏗️ INFRAESTRUCTURA (Docker & Jenkins) +# ================================================================= +# Nombres que tomarán los contenedores en Docker +APP_CONTAINER_NAME= +DB_CONTAINER_NAME= + +# Nombre del proyecto para Docker Compose (usado en Jenkins) +PROJECT_NAME= + +# Puertos que se abrirán al exterior (Host) +PORT= +DATABASE_EXPOSE_PORT= + +# ================================================================= +# 🐍 CONFIGURACIÓN DE DJANGO +# ================================================================= +# True para desarrollo, False para producción +DEBUG= +# Genera una clave segura: https://djecrety.ir/ +SECRET_KEY= +# Lista separada por comas: localhost,127.0.0.1,tudominio.com +ALLOWED_HOSTS= + +# ================================================================= +# 🗄️ BASE DE DATOS (PostgreSQL) +# ================================================================= +DB_NAME= +DB_USER= +DB_PASSWORD= + +# El HOST debe ser "db" cuando se usa Docker Compose +DB_HOST= +DB_PORT= \ No newline at end of file diff --git a/deployments/Dockerfile b/deployments/Dockerfile index af963c3..fd3fd91 100644 --- a/deployments/Dockerfile +++ b/deployments/Dockerfile @@ -4,27 +4,31 @@ FROM python:3.12-slim # Evitar que Python genere archivos .pyc y que el buffer se sature ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 -# Definimos la zona horaria como variable de entorno ENV TZ=Europe/Madrid + # Directorio de trabajo WORKDIR /app -# Instalar dependencias del sistema necesarias +# Instalar dependencias del sistema necesarias y limpiar caché en un solo paso RUN apt-get update && apt-get install -y \ libpq-dev \ gcc \ gettext \ && rm -rf /var/lib/apt/lists/* -# Instalar dependencias de Python -COPY requirements.txt /app/ +# Copiar dependencias (Ajustado: asumiendo que están en deployments/) +COPY deployments/requirements.txt /app/ RUN pip install --no-cache-dir -r requirements.txt -# Copiar el resto del código +# Copiar el resto del código del proyecto +# El contexto de docker-compose suele ser la raíz, por lo que copiamos todo a /app COPY . /app/ # Exponer el puerto de Django EXPOSE 8000 -# Comando por defecto para arrancar (usaremos manage.py en dev y gunicorn en prod) -CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] \ No newline at end of file +# Script de entrada (lo crearemos en el siguiente paso) +COPY deployments/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/deployments/Jenkinsfile b/deployments/Jenkinsfile index 7bb4a3a..fcd0924 100644 --- a/deployments/Jenkinsfile +++ b/deployments/Jenkinsfile @@ -5,37 +5,55 @@ pipeline { stage('Configurar Entorno') { steps { script { + // Seleccionamos ID de credencial y config según la rama if (env.BRANCH_NAME == 'master') { env.PROJECT_NAME = "django_master" - env.CONTAINER_NAME = "django_app_master" + env.APP_CONTAINER_NAME = "django_app_master" env.PORT = "8001" env.DEBUG_MODE = "0" + env.ENV_CREDENTIAL_ID = "2" // Tu ID para master } else { env.PROJECT_NAME = "django_dev" - env.CONTAINER_NAME = "django_app_dev" + env.APP_CONTAINER_NAME = "django_app_dev" env.PORT = "8000" env.DEBUG_MODE = "1" + env.ENV_CREDENTIAL_ID = "1" // Tu ID para dev } } } } - stage('Despliegue') { + stage('Fase Final: Containerización') { when { anyOf { branch 'dev'; branch 'master' } } steps { - echo "DESPLEGANDO: ${env.CONTAINER_NAME} en el puerto ${env.PORT}" - - // CAMBIAMOS docker-compose (guion) por docker compose (espacio) - sh """ - CONTAINER_NAME=${env.CONTAINER_NAME} \ - PORT=${env.PORT} \ - DEBUG_MODE=${env.DEBUG_MODE} \ - docker compose -p ${env.PROJECT_NAME} -f deployments/docker-compose.yml up -d --build web - """ - - echo "Ejecutando migraciones en ${env.CONTAINER_NAME}..." - sh "docker exec ${env.CONTAINER_NAME} python manage.py migrate --noinput" + // Bloque mágico: extrae el archivo .env de la bóveda de Jenkins + withCredentials([file(credentialsId: env.ENV_CREDENTIAL_ID, variable: 'SECRET_ENV')]) { + sh """ + echo "Copiando configuración segura..." + cp \$SECRET_ENV deployments/.env + + echo "🚀 DESPLEGANDO: ${env.APP_CONTAINER_NAME} en puerto ${env.PORT}" + + export APP_CONTAINER_NAME=${env.APP_CONTAINER_NAME} + export PORT=${env.PORT} + export DEBUG_MODE=${env.DEBUG_MODE} + + docker compose -p ${env.PROJECT_NAME} -f deployments/docker-compose.yml up -d --build web + """ + } } } } + + post { + success { + echo "✅ Despliegue completado con éxito." + // Limpieza preventiva: borramos el .env físico después del despliegue + sh "rm -f deployments/.env" + } + failure { + echo "❌ Error en el despliegue. Revisa los logs." + sh "rm -f deployments/.env" + } + } } \ No newline at end of file diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index 4d2c46c..a3cdb79 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -1,36 +1,42 @@ services: db: image: postgres:15 - container_name: django_db_local - # Cargamos el archivo directamente - env_file: - - ../.env + container_name: ${DB_CONTAINER_NAME:-django_db_local} + restart: always + # Solo usamos el .env de esta carpeta + env_file: .env environment: - # POSTGRES_DB espera estas variables exactas, - # así que las mapeamos a lo que tienes en tu .env - POSTGRES_DB=${DB_NAME} - POSTGRES_USER=${DB_USER} - POSTGRES_PASSWORD=${DB_PASSWORD} ports: - - "5432:5432" + - "${DATABASE_EXPOSE_PORT:-5432}:5432" volumes: - local_postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"] + interval: 5s + timeout: 5s + retries: 5 web: - build: . - container_name: django_app_dev + build: + context: .. + dockerfile: deployments/Dockerfile + container_name: ${APP_CONTAINER_NAME:-django_app_dev} + restart: always volumes: - ..:/app - # Cargamos el archivo directamente aquí también - env_file: - - ../.env + ports: + - "${PORT:-8000}:8000" + # Inyectamos el .env local al contenedor web + env_file: .env environment: - DB_HOST=db - DB_PORT=5432 - ports: - - "8000:8000" depends_on: - - db + db: + condition: service_healthy volumes: local_postgres_data: \ No newline at end of file diff --git a/deployments/entrypoint.sh b/deployments/entrypoint.sh new file mode 100644 index 0000000..115690f --- /dev/null +++ b/deployments/entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Salir inmediatamente si un comando falla +set -e + +echo "--> Ejecutando migraciones..." +python manage.py makemigrations --noinput +python manage.py migrate --noinput + +# En el futuro, aquí podrías añadir: +# python manage.py collectstatic --noinput + +echo "--> Arrancando el servidor Django..." +# Usamos exec para que Django sea el proceso principal (PID 1) y reciba señales de Docker +exec python manage.py runserver 0.0.0.0:8000 \ No newline at end of file