Subida a producción
This commit is contained in:
parent
c349d12f9d
commit
b8e250e417
31
Dockerfile
Normal file
31
Dockerfile
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# syntax=docker/dockerfile:1.4
|
||||||
|
|
||||||
|
# Usa una imagen base de Python
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
APP_HOME="/app"
|
||||||
|
|
||||||
|
|
||||||
|
WORKDIR ${APP_HOME}
|
||||||
|
|
||||||
|
# Instalar librerías cliente Firebird
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get install libfbclient2 -y
|
||||||
|
|
||||||
|
# Copiamos solo lo necesario para instalar el paquete
|
||||||
|
COPY pyproject.toml setup.cfg README.md ./
|
||||||
|
COPY app ./app
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir .
|
||||||
|
|
||||||
|
# Copiar enviroment (se sobreescribe en compose)
|
||||||
|
#COPY enviroment/ ./enviroment
|
||||||
|
|
||||||
|
# Volumen para logs persistentes
|
||||||
|
#VOLUME ["/app/logs"]
|
||||||
|
|
||||||
|
# Entrypoint genérico
|
||||||
|
#CMD ["python", "-m", "sync_factuges_main"]
|
||||||
|
CMD ["factuges-sync", "all"]
|
||||||
55
app/cli.py
55
app/cli.py
@ -3,42 +3,65 @@
|
|||||||
# Permite:
|
# Permite:
|
||||||
# factuges-sync factuges
|
# factuges-sync factuges
|
||||||
# factuges-sync verifactu
|
# factuges-sync verifactu
|
||||||
|
# factuges-sync all
|
||||||
|
#
|
||||||
|
# También por variable de entorno:
|
||||||
# SYNC_MODE=factuges factuges-sync
|
# SYNC_MODE=factuges factuges-sync
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
from typing import Literal, Optional
|
||||||
|
|
||||||
|
Mode = Literal["factuges", "verifactu", "all"]
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def run_module(module: str) -> None:
|
||||||
|
"""Lanza un módulo Python como subproceso y falla en caso de error."""
|
||||||
|
subprocess.run([sys.executable, "-m", module], check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_mode(arg_mode: Optional[str]) -> Mode:
|
||||||
|
"""Resuelve el modo desde CLI o variable de entorno."""
|
||||||
|
mode = arg_mode or os.getenv("SYNC_MODE")
|
||||||
|
|
||||||
|
valid_modes: tuple[Mode, ...] = ("factuges", "verifactu", "all")
|
||||||
|
if mode not in valid_modes:
|
||||||
|
print(
|
||||||
|
"Error: debes indicar modo: 'factuges', 'verifactu' "
|
||||||
|
"o 'all' para ejecutar ambos"
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
return mode # type: ignore[return-value]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
"""Selector de modos de sincronización."""
|
"""Selector de modos de sincronización."""
|
||||||
parser = argparse.ArgumentParser(description="Factuges Sync Dispatcher")
|
parser = argparse.ArgumentParser(description="Factuges Sync Dispatcher")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"mode",
|
"mode",
|
||||||
nargs="?",
|
nargs="?",
|
||||||
choices=["factuges", "verifactu"],
|
choices=["factuges", "verifactu", "all"],
|
||||||
help="Modo de sincronización",
|
help="Modo de sincronización",
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
mode = args.mode or os.getenv("SYNC_MODE")
|
mode = resolve_mode(args.mode)
|
||||||
|
|
||||||
if mode not in ("factuges", "verifactu"):
|
if os.getenv("ENV") == "development":
|
||||||
print("Error: debes indicar modo: 'factuges' o 'verifactu'")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if os.getenv("ENV") == "developement":
|
|
||||||
print("Running in development mode (no docker)")
|
print("Running in development mode (no docker)")
|
||||||
|
|
||||||
module = (
|
if mode == "factuges":
|
||||||
"app.sync_factuges_main"
|
run_module("app.sync_factuges_main")
|
||||||
if mode == "factuges"
|
elif mode == "verifactu":
|
||||||
else "app.sync_verifactu_main"
|
run_module("app.sync_verifactu_main")
|
||||||
)
|
else: # mode == "all"
|
||||||
|
# Primero sincroniza FactuGES, luego Verifactu.
|
||||||
# Ejecuta el módulo Python correspondiente
|
# Si la primera falla, el proceso termina por el check=True.
|
||||||
subprocess.run([sys.executable, "-m", module], check=True)
|
run_module("app.sync_factuges_main")
|
||||||
|
run_module("app.sync_verifactu_main")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
from os.path import join, dirname
|
from os.path import dirname, join
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict
|
||||||
from .setup_logger import logger
|
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
from .setup_logger import logger
|
||||||
|
|
||||||
|
|
||||||
def _required(name: str) -> str:
|
def _required(name: str) -> str:
|
||||||
"""
|
"""
|
||||||
@ -43,7 +44,7 @@ def load_config() -> Dict[str, Any]:
|
|||||||
# Opcionales (con valor por defecto)
|
# Opcionales (con valor por defecto)
|
||||||
"ENV": env,
|
"ENV": env,
|
||||||
"LOCAL_TZ": os.getenv("LOCAL_TZ", "Europe/Madrid"),
|
"LOCAL_TZ": os.getenv("LOCAL_TZ", "Europe/Madrid"),
|
||||||
"LAST_RUN_PATH": _required("LAST_RUN_PATH"),
|
"STATE_PATH": _required("STATE_PATH"),
|
||||||
|
|
||||||
# FACTUGES (requeridas)
|
# FACTUGES (requeridas)
|
||||||
"FACTUGES_HOST": _required("FACTUGES_HOST"),
|
"FACTUGES_HOST": _required("FACTUGES_HOST"),
|
||||||
|
|||||||
@ -1,49 +1,68 @@
|
|||||||
|
# app/logger.py
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
|
||||||
def create_logger(
|
def create_logger(
|
||||||
name: str = "factuges-sync",
|
name: str = "factuges-sync",
|
||||||
*,
|
*,
|
||||||
level: int = logging.INFO,
|
level: int = logging.INFO,
|
||||||
log_path: str | None = None,
|
log_path: Optional[Union[str, Path]] = None,
|
||||||
|
max_bytes: int = 5_000_000, # rotación opcional
|
||||||
|
backup_count: int = 3,
|
||||||
) -> logging.Logger:
|
) -> logging.Logger:
|
||||||
"""
|
"""
|
||||||
Crea un logger:
|
Crea un logger consistente para FactuGES Sync.
|
||||||
- SIEMPRE stdout (Docker-friendly)
|
|
||||||
- SOLO EN PRODUCCIÓN añade RotatingFileHandler si log_path no es None
|
Reglas:
|
||||||
|
- SIEMPRE envia logs a stdout (Docker-friendly).
|
||||||
|
- SOLO en producción escribe también a fichero si `log_path` está definido.
|
||||||
|
- `log_path` puede ser `str` o `Path`.
|
||||||
|
- Evita duplicar handlers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger = logging.getLogger(name)
|
logger = logging.getLogger(name)
|
||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
|
|
||||||
# No duplicar handlers si ya existe el logger
|
# Si ya está configurado, no duplicamos handlers
|
||||||
if logger.handlers:
|
if logger.handlers:
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
formatter = logging.Formatter(
|
||||||
|
"%(asctime)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------
|
||||||
# 1) Consola → SIEMPRE (para docker logs)
|
# 1) Handler de consola (siempre)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------
|
||||||
h_console = logging.StreamHandler(sys.stdout)
|
h_console = logging.StreamHandler(sys.stdout)
|
||||||
h_console.setFormatter(formatter)
|
h_console.setFormatter(formatter)
|
||||||
logger.addHandler(h_console)
|
logger.addHandler(h_console)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------
|
||||||
# 2) Fichero → SOLO en prod + si se define log_path
|
# 2) Handler de fichero (solo prod)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------
|
||||||
environment = os.getenv("ENV", "development")
|
environment = os.getenv("ENV", "development").lower()
|
||||||
if environment == "production" and log_path:
|
|
||||||
Path(log_path).parent.mkdir(parents=True, exist_ok=True)
|
if log_path and environment in ("production", "prod"):
|
||||||
|
p = Path(log_path)
|
||||||
|
|
||||||
|
# Aseguramos directorios
|
||||||
|
p.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Puedes usar FileHandler simple, pero Rotating es más seguro.
|
||||||
h_file = RotatingFileHandler(
|
h_file = RotatingFileHandler(
|
||||||
log_path,
|
filename=str(p),
|
||||||
maxBytes=5 * 1024 * 1024,
|
maxBytes=max_bytes,
|
||||||
backupCount=15,
|
backupCount=backup_count,
|
||||||
encoding="utf8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
h_file.setFormatter(formatter)
|
h_file.setFormatter(formatter)
|
||||||
logger.addHandler(h_file)
|
logger.addHandler(h_file)
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from app.config import get_package_version
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from dateutil import tz
|
from dateutil import tz
|
||||||
from app.config import create_logger, load_config, log_config
|
|
||||||
from app.db import get_mysql_connection, get_factuges_connection, sync_invoices_factuges
|
from app.config import create_logger, get_package_version, load_config, log_config
|
||||||
from app.utils import obtener_fecha_ultima_ejecucion, actualizar_fecha_ultima_ejecucion
|
from app.db import get_factuges_connection, get_mysql_connection, sync_invoices_factuges
|
||||||
|
from app.utils import actualizar_fecha_ultima_ejecucion, obtener_fecha_ultima_ejecucion
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -14,10 +15,14 @@ def main():
|
|||||||
version = get_package_version()
|
version = get_package_version()
|
||||||
local_tz = tz.gettz(config['LOCAL_TZ'])
|
local_tz = tz.gettz(config['LOCAL_TZ'])
|
||||||
|
|
||||||
|
state_path = Path(config["STATE_PATH"])
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
|
log_dir = state_path / "logs"
|
||||||
|
log_dir.mkdir(parents=True, exist_ok=True)
|
||||||
logger = create_logger(
|
logger = create_logger(
|
||||||
name="factuges-sync",
|
name="factuges-sync",
|
||||||
log_path="/app/logs/sync_factuges.log", # Solo lo genera en producción
|
log_path=log_dir / "sync_factuges.log", # Solo lo genera en producción
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info("============================================================")
|
logger.info("============================================================")
|
||||||
@ -28,11 +33,12 @@ def main():
|
|||||||
|
|
||||||
log_config(config)
|
log_config(config)
|
||||||
|
|
||||||
|
state_file = state_path / "factuges_last.ini"
|
||||||
conn_factuges = None
|
conn_factuges = None
|
||||||
conn_mysql = None
|
conn_mysql = None
|
||||||
try:
|
try:
|
||||||
# Obtener la fecha de la última ejecución del programa
|
# Obtener la fecha de la última ejecución del programa
|
||||||
last_execution_date_utc = obtener_fecha_ultima_ejecucion(config['LAST_RUN_PATH'])
|
last_execution_date_utc = obtener_fecha_ultima_ejecucion(state_file)
|
||||||
last_execution_date_local_tz = last_execution_date_utc.astimezone(
|
last_execution_date_local_tz = last_execution_date_utc.astimezone(
|
||||||
tz=local_tz).strftime("%Y-%m-%d %H:%M:%S")
|
tz=local_tz).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
@ -47,7 +53,7 @@ def main():
|
|||||||
|
|
||||||
# Sincronizamos
|
# Sincronizamos
|
||||||
logger.info(
|
logger.info(
|
||||||
f">>>>>>>>>>> INI Sync invoices FactuGES escritorio to FactuGES web")
|
">>>>>>>>>>> INI Sync invoices FactuGES escritorio to FactuGES web")
|
||||||
sync_invoices_factuges(conn_factuges, conn_mysql, last_execution_date_local_tz)
|
sync_invoices_factuges(conn_factuges, conn_mysql, last_execution_date_local_tz)
|
||||||
|
|
||||||
# Confirmar los cambios
|
# Confirmar los cambios
|
||||||
@ -55,9 +61,9 @@ def main():
|
|||||||
conn_factuges.commit()
|
conn_factuges.commit()
|
||||||
conn_factuges.close()
|
conn_factuges.close()
|
||||||
conn_mysql.close()
|
conn_mysql.close()
|
||||||
logger.info(f">>>>>>>>>>> FIN Sync invoices FactuGES escritorio to FactuGES web")
|
logger.info(">>>>>>>>>>> FIN Sync invoices FactuGES escritorio to FactuGES web")
|
||||||
|
|
||||||
actualizar_fecha_ultima_ejecucion(config['LAST_RUN_PATH'])
|
actualizar_fecha_ultima_ejecucion(state_file)
|
||||||
|
|
||||||
# Enviar email
|
# Enviar email
|
||||||
# send_orders_mail(inserted_orders)
|
# send_orders_mail(inserted_orders)
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from app.config import get_package_version
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from dateutil import tz
|
from dateutil import tz
|
||||||
from app.config import create_logger, load_config, log_config
|
|
||||||
from app.db import get_mysql_connection, get_factuges_connection, sync_invoices_verifactu
|
from app.config import create_logger, get_package_version, load_config, log_config
|
||||||
from app.utils import obtener_fecha_ultima_ejecucion, actualizar_fecha_ultima_ejecucion
|
from app.db import get_mysql_connection, sync_invoices_verifactu
|
||||||
|
from app.utils import actualizar_fecha_ultima_ejecucion, obtener_fecha_ultima_ejecucion
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -15,10 +16,14 @@ def main():
|
|||||||
version = get_package_version()
|
version = get_package_version()
|
||||||
local_tz = tz.gettz(config['LOCAL_TZ'])
|
local_tz = tz.gettz(config['LOCAL_TZ'])
|
||||||
|
|
||||||
|
state_path = Path(config["STATE_PATH"])
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
|
log_dir = state_path / "logs"
|
||||||
|
log_dir.mkdir(parents=True, exist_ok=True)
|
||||||
logger = create_logger(
|
logger = create_logger(
|
||||||
name="factuges-sync",
|
name="factuges-sync",
|
||||||
log_path="/app/logs/sync_verifactu.log", # Solo lo genera en producción
|
log_path=log_dir / "sync_verifactu.log", # Solo lo genera en producción
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info("============================================================")
|
logger.info("============================================================")
|
||||||
@ -29,11 +34,12 @@ def main():
|
|||||||
|
|
||||||
log_config(config)
|
log_config(config)
|
||||||
|
|
||||||
|
state_file = Path(config["STATE_PATH"]) / "verifactu_last.ini"
|
||||||
conn_factuges = None
|
conn_factuges = None
|
||||||
conn_mysql = None
|
conn_mysql = None
|
||||||
try:
|
try:
|
||||||
# Obtener la fecha de la última ejecución del programa
|
# Obtener la fecha de la última ejecución del programa
|
||||||
last_execution_date_utc = obtener_fecha_ultima_ejecucion()
|
last_execution_date_utc = obtener_fecha_ultima_ejecucion(state_file)
|
||||||
last_execution_date_local_tz = last_execution_date_utc.astimezone(
|
last_execution_date_local_tz = last_execution_date_utc.astimezone(
|
||||||
tz=local_tz).strftime("%Y-%m-%d %H:%M:%S")
|
tz=local_tz).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
@ -47,13 +53,13 @@ def main():
|
|||||||
|
|
||||||
# Sync Verifactu
|
# Sync Verifactu
|
||||||
logger.info(
|
logger.info(
|
||||||
f">>>>>>>>>> INI Sync facturas emitidas to Verifactu")
|
">>>>>>>>>> INI Sync facturas emitidas to Verifactu")
|
||||||
sync_invoices_verifactu(conn_mysql, last_execution_date_local_tz)
|
sync_invoices_verifactu(conn_mysql, last_execution_date_local_tz)
|
||||||
conn_mysql.commit()
|
conn_mysql.commit()
|
||||||
conn_mysql.close()
|
conn_mysql.close()
|
||||||
logger.info(f">>>>>>>>>> FIN Sync facturas emitidas to Verifactu")
|
logger.info(">>>>>>>>>> FIN Sync facturas emitidas to Verifactu")
|
||||||
|
|
||||||
actualizar_fecha_ultima_ejecucion()
|
actualizar_fecha_ultima_ejecucion(state_file)
|
||||||
|
|
||||||
# Enviar email
|
# Enviar email
|
||||||
# send_orders_mail(inserted_orders)
|
# send_orders_mail(inserted_orders)
|
||||||
|
|||||||
@ -2,74 +2,68 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
# Fecha por defecto si nunca se ha ejecutado
|
||||||
DEFAULT_PATH = Path("last_execution.txt")
|
DEFAULT_FALLBACK = datetime(2025, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
|
||||||
DEFAULT_FALLBACK = datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
|
FMT = "%Y-%m-%d %H:%M:%S" # Formato persistido
|
||||||
FMT = "%Y-%m-%d %H:%M:%S"
|
|
||||||
|
|
||||||
|
|
||||||
def obtener_fecha_ultima_ejecucion(
|
def obtener_fecha_ultima_ejecucion(
|
||||||
path: str = DEFAULT_PATH,
|
path: str | Path,
|
||||||
*,
|
*,
|
||||||
fallback: Optional[datetime] = None,
|
fallback: Optional[datetime] = None,
|
||||||
) -> datetime:
|
) -> datetime:
|
||||||
"""
|
"""
|
||||||
Lee la última fecha de ejecución desde `path` y la devuelve como aware (UTC).
|
Lee la última fecha de ejecución almacenada en `path`.
|
||||||
|
Retorna always-aware UTC.
|
||||||
|
|
||||||
- Si el fichero no existe o el contenido es inválido, devuelve `fallback`.
|
- Si el archivo no existe → fallback
|
||||||
- Si `fallback` es None, usa DEFAULT_FALLBACK (2024-01-01T00:00:00Z).
|
- Si está vacío o el formato es inválido → fallback
|
||||||
|
|
||||||
|
fallback predeterminado: DEFAULT_FALLBACK (2025-01-01)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Comentario: fallback explícito para evitar lógica duplicada en llamadas
|
|
||||||
effective_fallback = fallback or DEFAULT_FALLBACK
|
effective_fallback = fallback or DEFAULT_FALLBACK
|
||||||
|
p = Path(path)
|
||||||
if path is None:
|
|
||||||
return effective_fallback
|
|
||||||
|
|
||||||
# 2. Convertimos str -> Path
|
|
||||||
path = Path(path)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
text = path.read_text(encoding="utf-8").strip()
|
text = p.read_text(encoding="utf-8").strip()
|
||||||
if not text:
|
if not text:
|
||||||
# Comentario: fichero vacío -> usamos fallback
|
|
||||||
return effective_fallback
|
return effective_fallback
|
||||||
|
|
||||||
dt_naive = datetime.strptime(text, FMT)
|
dt_naive = datetime.strptime(text, FMT)
|
||||||
return dt_naive.replace(tzinfo=timezone.utc)
|
return dt_naive.replace(tzinfo=timezone.utc)
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return effective_fallback
|
return effective_fallback
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# Comentario: formato inválido en el archivo -> fallback
|
|
||||||
return effective_fallback
|
return effective_fallback
|
||||||
|
|
||||||
|
|
||||||
def actualizar_fecha_ultima_ejecucion(
|
def actualizar_fecha_ultima_ejecucion(
|
||||||
path: str = DEFAULT_PATH,
|
path: str | Path,
|
||||||
*,
|
*,
|
||||||
momento: Optional[datetime] = None,
|
momento: Optional[datetime] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Escribe en `path` la fecha/hora (UTC) en formato YYYY-MM-DD HH:MM:SS.
|
Guarda `momento` (UTC) en `path`, creando directorios si hace falta.
|
||||||
Si `momento` es None, usa ahora en UTC.
|
|
||||||
Crea directorios intermedios si no existen.
|
- Si `momento` es None → ahora en UTC
|
||||||
|
- Si `momento` viene naive → se asume UTC
|
||||||
|
- Si trae tz → se convierte a UTC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
p = Path(path)
|
||||||
|
|
||||||
if momento is None:
|
if momento is None:
|
||||||
momento = datetime.now(timezone.utc)
|
momento = datetime.now(timezone.utc)
|
||||||
else:
|
else:
|
||||||
# Normalizamos a UTC si viene con tz; si es naive, asumimos UTC
|
|
||||||
if momento.tzinfo is None:
|
if momento.tzinfo is None:
|
||||||
momento = momento.replace(tzinfo=timezone.utc)
|
momento = momento.replace(tzinfo=timezone.utc)
|
||||||
else:
|
else:
|
||||||
momento = momento.astimezone(timezone.utc)
|
momento = momento.astimezone(timezone.utc)
|
||||||
|
|
||||||
# Asegurar carpeta si `path` incluye directorios
|
p.parent.mkdir(parents=True, exist_ok=True)
|
||||||
folder = os.path.dirname(os.path.abspath(path))
|
|
||||||
if folder and not os.path.exists(folder):
|
|
||||||
os.makedirs(folder, exist_ok=True)
|
|
||||||
|
|
||||||
with open(path, "w", encoding="utf8") as f:
|
p.write_text(momento.strftime(FMT), encoding="utf-8")
|
||||||
f.write(momento.strftime(FMT))
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
ENV = development
|
ENV = development
|
||||||
LOCAL_TZ = Europe/Madrid
|
LOCAL_TZ = Europe/Madrid
|
||||||
LAST_RUN_PATH = ./app.last_run.txt
|
STATE_PATH = ./
|
||||||
#LOG_PATH = ./app.log
|
#LOG_PATH = ./app.log
|
||||||
|
|
||||||
#DESARROLLO ACANA
|
#DESARROLLO ACANA
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
ENV = development
|
ENV = development
|
||||||
LOCAL_TZ = Europe/Madrid
|
LOCAL_TZ = Europe/Madrid
|
||||||
LAST_RUN_PATH = ./app.last_run.txt
|
STATE_PATH = ./
|
||||||
#LOG_PATH = ./app.log
|
#LOG_PATH = ./app.log
|
||||||
|
|
||||||
#DESARROLLO ACANA
|
#DESARROLLO ACANA
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
ENV = development
|
ENV = development
|
||||||
LOCAL_TZ = Europe/Madrid
|
LOCAL_TZ = Europe/Madrid
|
||||||
LAST_RUN_PATH = ./app.last_run.txt
|
STATE_PATH = ./
|
||||||
#LOG_PATH = ./app.log
|
#LOG_PATH = ./app.log
|
||||||
|
|
||||||
#DESARROLLO
|
#DESARROLLO
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
ENV = development
|
ENV = development
|
||||||
LOCAL_TZ = Europe/Madrid
|
LOCAL_TZ = Europe/Madrid
|
||||||
LAST_RUN_PATH = ./app.last_run.txt
|
STATE_PATH = ./
|
||||||
|
|
||||||
#LOG_PATH = ./app.log
|
#LOG_PATH = ./app.log
|
||||||
|
|
||||||
#DESARROLLO ACANA
|
#DESARROLLO ACANA
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# SYNC
|
# SYNC
|
||||||
ENV = development
|
ENV = development
|
||||||
LOCAL_TZ = Europe/Madrid
|
LOCAL_TZ = Europe/Madrid
|
||||||
LAST_RUN_PATH = /usr/share/factuges-app/last_run_factuges.ini
|
STATE_PATH = /app/state
|
||||||
|
|
||||||
FACTUGES_HOST = acana.mywire.org
|
FACTUGES_HOST = acana.mywire.org
|
||||||
FACTUGES_PORT = 63050
|
FACTUGES_PORT = 63050
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_VERSION="1.1.0"
|
SCRIPT_VERSION="2.0.0"
|
||||||
|
|
||||||
# ================================================
|
# ================================================
|
||||||
# FACTUGES SYNC - Docker Build Script
|
# FACTUGES SYNC - Docker Build Script (Simplificado)
|
||||||
# -----------------------------------------------
|
# -----------------------------------------------
|
||||||
# Uso:
|
# Uso:
|
||||||
# ./build.sh <company> [--load]
|
# ./build.sh <company> [--load]
|
||||||
@ -12,7 +12,7 @@ SCRIPT_VERSION="1.1.0"
|
|||||||
|
|
||||||
# ---------- 1. Validación ----------
|
# ---------- 1. Validación ----------
|
||||||
if [[ $# -eq 0 || "$1" == --* ]]; then
|
if [[ $# -eq 0 || "$1" == --* ]]; then
|
||||||
echo "❌ ERROR: Falta el parámetro <company>"
|
echo "ERROR: Falta el parámetro <company>"
|
||||||
echo "Uso: ./build.sh <company> [--load]"
|
echo "Uso: ./build.sh <company> [--load]"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@ -31,64 +31,45 @@ mkdir -p "$OUT_DIR"
|
|||||||
# ---------- 3. Info ----------
|
# ---------- 3. Info ----------
|
||||||
DATE=$(date +'%Y%m%d-%H%M%S')
|
DATE=$(date +'%Y%m%d-%H%M%S')
|
||||||
ISO_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
ISO_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
USER_NAME=$(whoami)
|
|
||||||
|
|
||||||
IMAGE_VERSION=$(sed -n 's/^version[[:space:]]*=[[:space:]]*\(.*\)$/\1/p' "$PROJECT_DIR/setup.cfg" | head -n1)
|
IMAGE_VERSION=$(sed -n 's/^version[[:space:]]*=[[:space:]]*\(.*\)$/\1/p' \
|
||||||
|
"$PROJECT_DIR/setup.cfg" | head -n1)
|
||||||
IMAGE_VERSION="${IMAGE_VERSION:-0.0.0}"
|
IMAGE_VERSION="${IMAGE_VERSION:-0.0.0}"
|
||||||
|
|
||||||
|
IMAGE_NAME="factuges-sync"
|
||||||
|
TAG_VERSION="${IMAGE_NAME}:${COMPANY}-${IMAGE_VERSION}"
|
||||||
|
TAG_LATEST="${IMAGE_NAME}:${COMPANY}-latest"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "-------------------------------------------------------"
|
echo "-------------------------------------------------------"
|
||||||
echo " FACTUGES SYNC Build Script v${SCRIPT_VERSION}"
|
echo " FACTUGES SYNC Build Script v${SCRIPT_VERSION}"
|
||||||
echo " Compañía: ${COMPANY}"
|
echo " Compañía: ${COMPANY}"
|
||||||
echo " Versión: ${IMAGE_VERSION}"
|
echo " Versión: ${IMAGE_VERSION}"
|
||||||
echo " Fecha: ${DATE}"
|
echo " Fecha: ${DATE}"
|
||||||
echo "-------------------------------------------------------"
|
echo "-------------------------------------------------------"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ---------- 4. Función para generar 1 build ----------
|
# ---------- 4. Build único ----------
|
||||||
build_image() {
|
echo "📦 Construyendo imagen Docker..."
|
||||||
local MODE="$1" # factuges | verifactu
|
|
||||||
|
|
||||||
local IMAGE_NAME="factuges-sync-${MODE}"
|
docker build --no-cache \
|
||||||
|
-t "${TAG_VERSION}" \
|
||||||
|
-t "${TAG_LATEST}" \
|
||||||
|
--build-arg COMPANY="${COMPANY}" \
|
||||||
|
-f "${PROJECT_DIR}/Dockerfile" "${PROJECT_DIR}"
|
||||||
|
|
||||||
local TAG_VERSION="${IMAGE_NAME}:${COMPANY}-${IMAGE_VERSION}"
|
echo "✅ Imagen construida: ${TAG_VERSION}"
|
||||||
local TAG_LATEST="${IMAGE_NAME}:${COMPANY}-latest"
|
|
||||||
|
|
||||||
echo "📦 Construyendo imagen Docker (${MODE})..."
|
# ---------- 5. Save tar ----------
|
||||||
|
TAR_V="${OUT_DIR}/${IMAGE_NAME}-${COMPANY}-v${IMAGE_VERSION}-${DATE}.tar"
|
||||||
|
TAR_LATEST="${OUT_DIR}/${IMAGE_NAME}-${COMPANY}-latest.tar"
|
||||||
|
|
||||||
docker build --no-cache \
|
docker save -o "${TAR_V}" "${TAG_VERSION}" "${TAG_LATEST}"
|
||||||
-t "${TAG_VERSION}" \
|
cp -f "${TAR_V}" "${TAR_LATEST}"
|
||||||
-t "${TAG_LATEST}" \
|
|
||||||
--build-arg COMPANY="${COMPANY}" \
|
|
||||||
-f "${PROJECT_DIR}/Dockerfile.${MODE}" "${PROJECT_DIR}"
|
|
||||||
|
|
||||||
echo "✅ Imagen construida: ${TAG_VERSION}"
|
echo "📦 Imagen guardada:"
|
||||||
|
echo " - ${TAR_V}"
|
||||||
local TAR_V="${OUT_DIR}/${IMAGE_NAME}-${COMPANY}-v${IMAGE_VERSION}-${DATE}.tar"
|
echo " - ${TAR_LATEST}"
|
||||||
local TAR_LATEST="${OUT_DIR}/${IMAGE_NAME}-${COMPANY}-latest.tar"
|
|
||||||
|
|
||||||
docker save -o "${TAR_V}" "${TAG_VERSION}" "${TAG_LATEST}"
|
|
||||||
cp -f "${TAR_V}" "${TAR_LATEST}"
|
|
||||||
|
|
||||||
echo "📦 Imagen guardada:"
|
|
||||||
echo " - ${TAR_V}"
|
|
||||||
echo " - ${TAR_LATEST}"
|
|
||||||
|
|
||||||
# Exportamos variables a nivel global para el LOAD opcional
|
|
||||||
echo "${TAR_V}"
|
|
||||||
echo "${TAR_LATEST}"
|
|
||||||
|
|
||||||
echo "${TAR_V}|${TAR_LATEST}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------- 5. Ejecutar build para ambos modos ----------
|
|
||||||
BUILD_OUT_FACTUGES=$(build_image "factuges")
|
|
||||||
FACTUGES_TAR_V=$(echo "$BUILD_OUT_FACTUGES" | cut -d '|' -f1)
|
|
||||||
FACTUGES_TAR_LATEST=$(echo "$BUILD_OUT_FACTUGES" | cut -d '|' -f2)
|
|
||||||
|
|
||||||
BUILD_OUT_VERIFACTU=$(build_image "verifactu")
|
|
||||||
VERIFACTU_TAR_V=$(echo "$BUILD_OUT_VERIFACTU" | cut -d '|' -f1)
|
|
||||||
VERIFACTU_TAR_LATEST=$(echo "$BUILD_OUT_VERIFACTU" | cut -d '|' -f2)
|
|
||||||
|
|
||||||
# ---------- 6. Manifest ----------
|
# ---------- 6. Manifest ----------
|
||||||
MANIFEST_FILE="${OUT_DIR}/manifest-${IMAGE_VERSION}-${DATE}.json"
|
MANIFEST_FILE="${OUT_DIR}/manifest-${IMAGE_VERSION}-${DATE}.json"
|
||||||
@ -98,13 +79,9 @@ cat > "${MANIFEST_FILE}" <<EOF
|
|||||||
"version": "${IMAGE_VERSION}",
|
"version": "${IMAGE_VERSION}",
|
||||||
"build_time": "${ISO_DATE}",
|
"build_time": "${ISO_DATE}",
|
||||||
"docker_images": {
|
"docker_images": {
|
||||||
"factuges": {
|
"sync": {
|
||||||
"versioned": "$(basename "${FACTUGES_TAR_V}")",
|
"versioned": "$(basename "${TAR_V}")",
|
||||||
"latest": "$(basename "${FACTUGES_TAR_LATEST}")"
|
"latest": "$(basename "${TAR_LATEST}")"
|
||||||
},
|
|
||||||
"verifactu": {
|
|
||||||
"versioned": "$(basename "${VERIFACTU_TAR_V}")",
|
|
||||||
"latest": "$(basename "${VERIFACTU_TAR_LATEST}")"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,32 +95,23 @@ echo ""
|
|||||||
if [[ "$LOAD" == true ]]; then
|
if [[ "$LOAD" == true ]]; then
|
||||||
echo "📥 Subiendo imágenes al servidor..."
|
echo "📥 Subiendo imágenes al servidor..."
|
||||||
|
|
||||||
# Subimos solo los .tar
|
|
||||||
scp -P 49152 "${OUT_DIR}"/*.tar \
|
scp -P 49152 "${OUT_DIR}"/*.tar \
|
||||||
rodax@vps-2.rodax-software.com:/opt/factuges/${COMPANY}/sync/
|
rodax@vps-2.rodax-software.com:/opt/factuges/${COMPANY}/sync/
|
||||||
|
|
||||||
scp -P 49152 deploy-cron.sh \
|
|
||||||
rodax@vps-2.rodax-software.com:/opt/factuges/${COMPANY}/
|
|
||||||
|
|
||||||
echo "📥 Cargando imágenes en Docker remoto..."
|
echo "📥 Cargando imágenes en Docker remoto..."
|
||||||
|
|
||||||
ssh -p 49152 rodax@vps-2.rodax-software.com <<EOF
|
ssh -p 49152 rodax@vps-2.rodax-software.com <<EOF
|
||||||
docker load -i /opt/factuges/${COMPANY}/sync/$(basename "${FACTUGES_TAR_V}")
|
docker load -i /opt/factuges/${COMPANY}/sync/$(basename "${TAR_V}")
|
||||||
docker load -i /opt/factuges/${COMPANY}/sync/$(basename "${FACTUGES_TAR_LATEST}")
|
docker load -i /opt/factuges/${COMPANY}/sync/$(basename "${TAR_LATEST}")
|
||||||
docker load -i /opt/factuges/${COMPANY}/sync/$(basename "${VERIFACTU_TAR_V}")
|
|
||||||
docker load -i /opt/factuges/${COMPANY}/sync/$(basename "${VERIFACTU_TAR_LATEST}")
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "✔ Todas las imágenes cargadas en producción"
|
echo "✔ Imágenes cargadas"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------- 8. Resumen ----------
|
# ---------- 8. Resumen ----------
|
||||||
echo ""
|
echo ""
|
||||||
echo "-------------------------------------------------------"
|
echo "-------------------------------------------------------"
|
||||||
echo "🎯 BUILD COMPLETADO PARA '${COMPANY}'"
|
echo "🎯 BUILD COMPLETADO PARA '${COMPANY}'"
|
||||||
echo " - factuges"
|
|
||||||
echo " - verifactu"
|
|
||||||
[[ "$LOAD" == true ]] && echo "✔ Load OK"
|
|
||||||
echo "🧩 Script version: ${SCRIPT_VERSION}"
|
echo "🧩 Script version: ${SCRIPT_VERSION}"
|
||||||
echo "-------------------------------------------------------"
|
echo "-------------------------------------------------------"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@ -6,7 +6,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
ENV: "production"
|
ENV: "production"
|
||||||
LOCAL_TZ: "Europe/Madrid"
|
LOCAL_TZ: "Europe/Madrid"
|
||||||
LAST_RUN_PATH: "${LAST_RUN_PATH}"
|
STATE_PATH: "${STATE_PATH}"
|
||||||
|
|
||||||
FACTUGES_HOST: "${FACTUGES_HOST}"
|
FACTUGES_HOST: "${FACTUGES_HOST}"
|
||||||
FACTUGES_PORT: "${FACTUGES_PORT}"
|
FACTUGES_PORT: "${FACTUGES_PORT}"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = factuges-sync
|
name = factuges-sync
|
||||||
version = 0.0.21
|
version = 0.0.25
|
||||||
description = ETL job to sync data from legacy DB to MariaDB
|
description = ETL job to sync data from legacy DB to MariaDB
|
||||||
author = Rodax Software
|
author = Rodax Software
|
||||||
author_email = info@rodax-software.com
|
author_email = info@rodax-software.com
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user