Initial commit: SaveUrl project migration from Drive

This commit is contained in:
Antigravity Migration Agent
2026-05-09 14:20:17 -04:00
commit 9f799c9398
16 changed files with 1439 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
# Documentación del proyecto `SaveUrl`
Esta carpeta contiene la documentación completa del proyecto (sin depender del `README.md`).
## Lectura rápida (por rol)
- **Operación / soporte**: empieza por `07_RUNBOOK_OPERACION.md` y `08_TROUBLESHOOTING.md`.
- **Despliegue en Airflow**: `04_DESPLIEGUE_AIRFLOW.md` + `03_CONFIGURACION_VARIABLES.md`.
- **Desarrollo / mantenimiento**: `05_FLUJO_Y_ARQUITECTURA.md` + `06_REFERENCIA_TECNICA_SAVEURL.md`.
- **Seguridad**: `09_SEGURIDAD_Y_SECRETS.md`.
## Documentos
1. `01_VISION_GENERAL.md`
2. `02_INSTALACION_LOCAL.md`
3. `03_CONFIGURACION_VARIABLES.md`
4. `04_DESPLIEGUE_AIRFLOW.md`
5. `05_FLUJO_Y_ARQUITECTURA.md`
6. `06_REFERENCIA_TECNICA_SAVEURL.md`
7. `07_RUNBOOK_OPERACION.md`
8. `08_TROUBLESHOOTING.md`
9. `09_SEGURIDAD_Y_SECRETS.md`
+68
View File
@@ -0,0 +1,68 @@
# Visión general
## ¿Qué hace este proyecto?
El proyecto **lee listados de archivos desde un servidor FTP**, **construye URLs públicas** para esos archivos y **guarda las URLs en SQL Server** dentro de una tabla llamada `URL_FTP`.
Durante la ejecución también:
- publica eventos de ejecución en **Kafka** (topic `executions_logs`)
- envía una **notificación por webhook** ante errores (mensaje tipo “alerta pipeline”)
El entrypoint operativo está pensado para ejecutarse desde **Apache Airflow** a través de un DAG.
## Componentes principales
- `SaveUrl.py`
- contiene la lógica de negocio (`run_save_url`)
- implementa:
- lectura FTP (`read_ftp`)
- lectura SQL filtrada por carpeta (`read_table_where`)
- inserción en SQL en lotes (`insert_records`)
- logger a Kafka (`KafkaLogger`)
- `dag_save_url_ftp.py`
- define el DAG `save_url_ftp` y ejecuta `run_save_url`
## Entornos y configuración
Este proyecto se configura con **variables de Airflow** (Airflow Variables). Aunque el `README.md` menciona `.env`, el código actual usa `airflow.models.Variable.get(...)` para obtener:
- credenciales SQL Server
- credenciales FTP
- configuración Kafka (incluyendo SSL/SASL)
- URL de webhook de notificación
## Frecuencia de ejecución
El DAG está programado con:
- cron: `0 */2 * * *` (**cada 2 horas**)
## Fuentes/“BDs” procesadas
En cada ejecución se itera sobre una lista fija (en `SaveUrl.py`) con entradas del tipo:
- nombre de carpeta base (ej. `glmpmi`)
- `identity_id` usado para parsear el nombre de archivo/URL
- nombre de “database” lógico usado para logs (ej. `glm_pmi`)
- `database_id` (para construir `procedure_id` del log)
Para cada entrada se calcula un `code_folder` con el mes actual (formato `MM`), por ejemplo: `glmpmi04`.
## Salida principal
- **Tabla destino**: `URL_FTP` (SQL Server)
- **Campos calculados por registro** (a partir de la URL):
- `Folder`
- `Date`
- `IDcodigo`
- `ObjectCode`
- `TaskName`
- `URL`
## Señales de observabilidad
- Logs en Airflow (logger estándar de Python)
- Logs “estructurados” en Kafka en `executions_logs`
- Notificación por webhook en fallos de FTP/SQL/INSERT
+73
View File
@@ -0,0 +1,73 @@
# Instalación local (desarrollo / diagnóstico)
## Objetivo
Aunque el flujo está pensado para ejecutarse en Airflow, esta guía sirve para:
- reproducir fallos fuera del scheduler
- validar conectividad a FTP/SQL/Kafka
- ejecutar pruebas manuales de parsing de URLs
## Requisitos
- Windows (PowerShell) o Linux/macOS con Python 3.x
- Driver ODBC para SQL Server (recomendado: **ODBC Driver 18 for SQL Server**)
- Acceso de red a:
- servidor FTP (`FTP_000oqta_SERVER`)
- SQL Server (`DB_MSSQ_SERVER`)
- Kafka (`BOOTSTRAP_SERVERS` o `BOOTSTRAP_SERVERS_DOCKER`, según corresponda)
## Crear entorno virtual
En PowerShell:
```powershell
cd d:\ProgramGLM\SaveUrl
python -m venv env
.\env\Scripts\Activate.ps1
python -m pip install --upgrade pip
```
## Instalar dependencias
El código importa al menos:
- `requests`
- `pyodbc`
- `kafka` (librería `kafka-python` / `kafka-python-ng`)
- `apache-airflow` (porque `SaveUrl.py` usa `airflow.models.Variable`)
Instalación típica (ajusta según tu entorno):
```powershell
pip install requests pyodbc kafka-python apache-airflow
```
> Nota: instalar Airflow localmente en Windows puede ser complejo. Si tu objetivo es **solo** ejecutar localmente, considera ejecutar en un contenedor Linux o adaptar temporalmente el código para leer variables desde `os.getenv`/`.env`. Esta documentación **no modifica** el código.
## Variables para ejecución local
El `SaveUrl.py` actual lee configuración desde **Airflow Variables** (`Variable.get`). Para ejecutar fuera de Airflow tienes 2 opciones:
1. **Ejecutar dentro de un entorno Airflow** (recomendado) y definir las Variables.
2. **Crear un “shim” local**: arrancar un script wrapper que inicialice Airflow y cargue Variables. (Esto depende de cómo esté configurado tu Airflow local).
Por eso, para validación local rápida sin Airflow, lo más útil suele ser:
- probar funciones aisladas (FTP/SQL) en un REPL, o
- ejecutar dentro de un contenedor Airflow/Composer donde ya existan las Variables.
## Ejecución manual (si ya estás dentro de Airflow)
Si Airflow ya está instalado/configurado y el archivo está en el path correcto, puedes invocar:
```powershell
python -c "from SaveUrl import run_save_url; run_save_url()"
```
## Verificación mínima
- **FTP**: confirma que existe el path base esperado: `FTP_BASE_PATH` + `<code_folder>`
- **SQL**: valida conexión ODBC al servidor y permisos de INSERT/SELECT sobre `URL_FTP`
- **Kafka**: valida que el broker esté accesible y el topic `executions_logs` exista (o que el broker permita auto-create)
@@ -0,0 +1,72 @@
# Configuración (Airflow Variables)
## Fuente de configuración real
El código en `SaveUrl.py` obtiene configuración principalmente desde **Airflow Variables** usando `airflow.models.Variable.get(...)`.
Esto implica:
- Debes definir estas Variables en tu instancia de Airflow (UI o CLI).
- El archivo `.env.example` sirve como **referencia de nombres**, pero **no es la fuente** en ejecución si corres bajo Airflow.
## Variables requeridas
### SQL Server
- `DB_MSSQ_SERVER`: hostname o IP del SQL Server
- `DB_MSSQ_USER`: usuario
- `DB_MSSQ_PASSWORD`: password
Notas:
- La conexión ODBC usa `ODBC Driver 18 for SQL Server`.
- La tabla destino es fija: `URL_FTP`.
- El nombre de “database” (catálogo) se toma de la lista `INFORMATIONS` dentro del código (ej. `glm_pmi`, `IS001`, etc.).
### FTP
- `FTP_000oqta_SERVER`
- `FTP_000oqta_USER`
- `FTP_000oqta_PASSWORD`
Notas:
- El código construye el path como `FTP_BASE_PATH + code_folder`.
- `code_folder` se calcula como `<name_folder><MM>` (mes actual), por ejemplo `glmpmi04`.
### Kafka
- `BOOTSTRAP_SERVERS_DOCKER`: brokers accesibles desde el entorno Airflow (por ejemplo `kafka-kraft:9092`)
- `KAFKA_USE_SSL`: `"True"` o `"False"`
Si `KAFKA_USE_SSL == "True"`, además se leen (desde variables de entorno del proceso, vía `os.getenv`, no Airflow Variables):
- `SECURITY_PROTOCOL` (ej. `SASL_SSL`)
- `SASL_MECHANISM` (ej. `SCRAM-SHA-256`)
- `SASL_PLAIN_USERNAME`
- `SASL_PLAIN_PASSWORD`
- `SSL_CAFILE` (path al CA dentro del contenedor/host)
- `SSL_CHECK_HOSTNAME` (`True`/`False`)
> Importante: en el código actual, `BOOTSTRAP_SERVERS_DOCKER` y `KAFKA_USE_SSL` se obtienen como Airflow Variables, pero los demás parámetros de seguridad se obtienen desde `os.getenv`. En despliegues en contenedores, asegúrate de definir esas variables en el **entorno del scheduler/worker**.
### Webhook de notificación
- `WEBHOOK_URL_NOTIFICATION`: endpoint que acepta `POST` con JSON `{"message": "..."}`
## Plantilla `.env.example`
El archivo `.env.example` contiene los nombres esperados (útil para referencia), pero:
- el proyecto **no debería** versionar un `.env` real con secretos
- en Airflow, se recomienda gestionar secretos con backend (Vault/Secrets Manager/etc.) o Variables protegidas
## Validación recomendada (pre-flight)
Antes de activar el DAG en producción, valida:
- Variables existen (sin typos)
- SQL user tiene `SELECT` e `INSERT` sobre `URL_FTP`
- FTP credenciales permiten `NLST` sobre el path base
- Kafka accesible desde el contenedor/worker que ejecuta el DAG
+75
View File
@@ -0,0 +1,75 @@
# Despliegue en Apache Airflow
## Qué se despliega
- DAG: `dag_save_url_ftp.py`
- Módulo Python: `SaveUrl.py` (debe ser importable por el DAG)
## Ruta esperada por el DAG
En `dag_save_url_ftp.py` se define un `sys.path.insert`:
- `sys.path.insert(0, '/opt/airflow/dags/save_url_ftp')`
Por tanto, dentro del contenedor/host donde corre Airflow, se espera:
- `/opt/airflow/dags/save_url_ftp/SaveUrl.py`
- `/opt/airflow/dags/save_url_ftp/dag_save_url_ftp.py`
> Si tu instalación usa otra ruta de `dags_folder`, ajusta el montaje/volumen para que esa estructura exista dentro del runtime.
## Parámetros del DAG
En `dag_save_url_ftp.py`:
- `dag_id`: `save_url_ftp`
- schedule: `0 */2 * * *` (cada 2 horas)
- `retries`: 2
- `retry_delay`: 5 minutos
- `catchup`: `False`
- `max_active_runs`: 1
Estructura de tareas:
- `inicio` (EmptyOperator) → `save_url_ftp` (PythonOperator) → `fin` (EmptyOperator)
## Variables de Airflow necesarias
Define las Variables descritas en `03_CONFIGURACION_VARIABLES.md`.
En particular, `SaveUrl.py` requiere:
- `WEBHOOK_URL_NOTIFICATION`
- `BOOTSTRAP_SERVERS_DOCKER`
- `KAFKA_USE_SSL`
- `FTP_000oqta_SERVER`, `FTP_000oqta_USER`, `FTP_000oqta_PASSWORD`
- `DB_MSSQ_USER`, `DB_MSSQ_PASSWORD`, `DB_MSSQ_SERVER`
## Permisos y conectividad
- **Worker/Scheduler** que ejecute el PythonOperator debe tener:
- salida a red hacia FTP, SQL Server y Kafka
- driver ODBC instalado (si se usa `pyodbc` en el worker)
## Operación esperada en Airflow
Durante la ejecución, el task `save_url_ftp` escribe logs tipo:
- inicio/final
- estado por “BD” lógica (cada item de `INFORMATIONS`)
- conteos: archivos FTP, registros existentes, registros a insertar
Si ocurre un error en FTP/SQL/INSERT:
- registra `FAILED` en Kafka
- envía webhook con el mensaje de alerta
- continúa con la siguiente “BD” (no falla toda la corrida por un único item)
## Checklist de despliegue
- DAG visible y sin errores de importación
- `SaveUrl.py` importable desde la ruta insertada en `sys.path`
- Variables creadas (y protegidas si aplica)
- Driver ODBC disponible en el runtime
- Conectividad validada (FTP/SQL/Kafka/webhook)
+99
View File
@@ -0,0 +1,99 @@
# Flujo y arquitectura
## Flujo end-to-end
El flujo, por cada entrada en `INFORMATIONS`, es:
1. Calcular `code_folder = <name_folder><MM>` (mes actual).
2. Leer lista de archivos del FTP en `FTP_BASE_PATH + code_folder`.
3. Convertir paths FTP a URLs públicas y **parsear metadatos** desde la URL.
4. Leer desde SQL Server la tabla `URL_FTP` filtrando por `Folder LIKE %code_folder%`.
5. Calcular diferencia por `IDcodigo`:
- insertar solo los registros nuevos (los no existentes en SQL).
6. Emitir logs a Kafka y escribir logs en Airflow.
7. En caso de error, registrar `FAILED` y notificar por webhook; continuar con el siguiente item.
## Diagrama (alto nivel)
```mermaid
flowchart LR
A[Airflow DAG save_url_ftp] --> B[PythonOperator: run_save_url]
B --> C{Por cada item en INFORMATIONS}
C --> D[FTP: nlst(path)]
D --> E[Construir URL pública + parse]
E --> F[SQL: SELECT por Folder]
F --> G{Comparar IDcodigo}
G -->|Nuevos| H[SQL: INSERT batches]
G -->|Sin nuevos| I[Sin INSERT]
D --> K[Kafka: executions_logs]
F --> K
H --> K
B --> L[Webhook: alerta en errores]
```
## Detalles del parsing (URL → campos)
`read_ftp()`:
- obtiene `file_names` via `ftp.nlst(path)`
- construye URL pública como:
- `FTP_BASE_URL + f.replace('/www', '')`
- por cada URL calcula:
- `Folder`: segmento `code_folder` dentro de la URL
- `Date`: substring encontrado con patrón `_{YYYY}-{MM}`
- `IDcodigo`: substring que empieza donde aparece `identity_id` y toma 24 caracteres
- `ObjectCode`: substring entre `Date` y `IDcodigo`
- `TaskName`: substring entre el fin de `code_folder + "/"` y el `_YYYY-MM...`
- `URL`: la URL pública final
Esto asume que los nombres de archivos/paths en el FTP siguen un patrón consistente, por ejemplo:
- contiene `_<YYYY>-<MM>` como parte del nombre
- contiene el `identity_id` en el nombre
## Interacción con SQL Server
### Lectura
`read_table_where()` ejecuta:
- `SELECT * FROM [URL_FTP] WHERE Folder LIKE '%<code_folder>%'`
Luego construye una lista de dicts con todas las columnas.
### Inserción
`insert_records()`:
- inserta en lotes de 1000 (`executemany`)
- arma la lista de columnas desde las llaves del primer dict, excluyendo:
- `ID`
- `ExportDate`
Implicaciones:
- La tabla `URL_FTP` debe tener (como mínimo) columnas que coincidan con las llaves calculadas por `read_ftp()` (o defaults/allow null).
- Si existen columnas extra en la tabla, el código **no** las insertará a menos que estén presentes en el dict.
## Observabilidad (Kafka)
El proyecto envía logs “estructurados” a Kafka con campos como:
- `database`, `procedure_id`, `procedure_name`
- `procedure_type` (fijo: `PYTHON_APP`)
- `status` (`STARTING`, `SUCCESS`, `FAILED`, `FINISHED`)
- `message`, `executed_by`, `source`
- `target_date`, `date`
- `extra_info` (serializado a string)
El `procedure_id` se construye como:
- `<database_id>` + `PROCEDURE_ID_SUFFIX`
## Decisiones y limitaciones actuales
- **Configuración mezclada**: parte se lee como Airflow Variables y parte como variables de entorno (`os.getenv`) para Kafka SSL/SASL.
- **Fechas**: usa la fecha/hora local del worker (`datetime.now()`), no el `execution_date` de Airflow.
- **No hay deduplicación por URL**: la deduplicación se hace por `IDcodigo` únicamente.
- **Errores por item**: si falla un item de `INFORMATIONS`, el script continúa con el siguiente.
@@ -0,0 +1,147 @@
# Referencia técnica (`SaveUrl.py`)
## Constantes relevantes
- `APP_NAME`: `PYTHON_CREATE_URL-FTP_APP`
- `VERSION`: `2.0.0`
- `PROCEDURE_ID_SUFFIX`: `000301900001`
- `FTP_BASE_URL`: `https://000oqta.rcomhost.com`
- `FTP_BASE_PATH`: `/www/imglm/2026/`
- `WEBHOOK_URL`: se obtiene desde Airflow Variable `WEBHOOK_URL_NOTIFICATION`
- `INFORMATIONS`: lista de fuentes a procesar (carpeta, identity_id, database lógico, database_id)
## Clase `KafkaLogger`
### Responsabilidad
Publica logs de ejecución a Kafka en el topic:
- `executions_logs`
### Inicialización
Parámetros:
- `source`: string (se usa `f"{APP_NAME}_{VERSION}"`)
- `bootstrap_servers`: lista/string de brokers
- `use_ssl`: boolean
Comportamiento:
- serializa `value` como JSON (`json.dumps(...).encode('utf-8')`)
- si `use_ssl` es `True`, agrega configuración SASL/SSL desde `os.getenv`
### Método `log(...)`
Firma (conceptual):
- `log(database, db_id, procedure_name, status, message, executed_by, target_date, date, extra_info=None)`
Payload enviado a Kafka (campos clave):
- `database`: nombre lógico (ej. `glm_pmi`)
- `procedure_id`: `str(db_id) + PROCEDURE_ID_SUFFIX`
- `procedure_name`: texto como `Generacion de URL <db>`
- `procedure_type`: `PYTHON_APP`
- `status`: `STARTING` | `SUCCESS` | `FAILED` | `FINISHED`
- `message`: descripción del evento
- `executed_by`: usuario SQL (`DB_MSSQ_USER`)
- `source`: `source` del logger
- `extra_info`: string (sanitiza comillas simples)
- `target_date`: fecha objetivo (`YYYY-MM-DD`)
- `date`: timestamp (string)
## Función `read_ftp(...)`
### Responsabilidad
Conecta al FTP y lista archivos en:
- `FTP_BASE_PATH + code_folder`
Luego construye URLs públicas y parsea metadatos en un dict.
Parámetros:
- `ftp_server`, `ftp_user`, `ftp_password`
- `code_folder`: carpeta calculada (`<name_folder><MM>`)
- `identity_id`: string para ubicar `IDcodigo` dentro del nombre
- `year`, `month`: se usan para encontrar `_{YYYY}-{MM}`
Salida:
- `list[dict]` con llaves: `Folder`, `Date`, `IDcodigo`, `ObjectCode`, `TaskName`, `URL`
Errores esperados:
- fallo de login o conectividad FTP
- path inexistente (depende de cómo responda `nlst`)
- parsing incorrecto si el nombre no contiene los patrones esperados
## Función `read_table_where(...)`
### Responsabilidad
Lee registros existentes en SQL Server desde una tabla, filtrando por `Folder`.
Query:
- `SELECT * FROM [URL_FTP] WHERE Folder LIKE '%<folder>%'`
Salida:
- lista de dicts (columna → valor)
Errores esperados:
- driver ODBC no instalado en runtime
- credenciales inválidas / permisos insuficientes
- tabla inexistente
## Función `insert_records(...)`
### Responsabilidad
Inserta registros en SQL Server en lotes de 1000.
Detalles:
- toma columnas desde el primer elemento del `data`
- excluye `ID` y `ExportDate` si existen en el dict
- arma un `INSERT INTO [URL_FTP] ([col1],...) VALUES (?,...)`
Salida:
- cantidad de registros insertados (int)
Errores esperados:
- tipos incompatibles con schema SQL
- restricciones (NOT NULL, UNIQUE) no satisfechas
- problemas de red/conexión
## Función `run_save_url(**context)`
### Responsabilidad
Orquesta toda la ejecución:
- inicializa `KafkaLogger`
- obtiene Variables de Airflow (FTP/SQL/Kafka/webhook)
- recorre `INFORMATIONS`
- para cada item:
- `read_ftp``read_table_where` → diff por `IDcodigo``insert_records`
- logging a Kafka por pasos
- notificación por webhook si hay errores
### Notificación (webhook)
En errores, construye un texto (formato “ALERTA PIPELINE”) y hace:
- `POST <WEBHOOK_URL_NOTIFICATION>` con JSON: `{"message": "<texto>"}`
### Consideraciones Airflow
- El parámetro `**context` permite que el callable sea usado por `PythonOperator`.
- La función usa `datetime.now()` (no usa `execution_date` de Airflow).
+66
View File
@@ -0,0 +1,66 @@
# Runbook de operación
## Objetivo operacional
Mantener la sincronización:
- FTP (archivos del mes/carpeta) → SQL Server (`URL_FTP`)
con observabilidad en Kafka y alerta por webhook.
## Qué esperar en una ejecución “sana”
Por cada item en `INFORMATIONS` se esperan, en logs de Airflow:
- `✅ FTP ... <N> archivos en <code_folder>`
- `✅ SQL ... <M> registros existentes`
- `️ DIFF ... A insertar: <K>`
- `✅ INSERT ... Insertados: <K>` (o warning de “Sin registros nuevos”)
Al final:
- `⏹ FINALIZADO ...`
## Inputs críticos (pre-flight)
- **Airflow Variables** existen y tienen valores válidos:
- FTP: `FTP_000oqta_SERVER`, `FTP_000oqta_USER`, `FTP_000oqta_PASSWORD`
- SQL: `DB_MSSQ_SERVER`, `DB_MSSQ_USER`, `DB_MSSQ_PASSWORD`
- Kafka: `BOOTSTRAP_SERVERS_DOCKER`, `KAFKA_USE_SSL`
- Webhook: `WEBHOOK_URL_NOTIFICATION`
- El worker donde corre el DAG tiene:
- driver ODBC 18 instalado
- conectividad a FTP/SQL/Kafka/webhook
## Procedimiento ante incidentes (rápido)
1. Revisar logs del task `save_url_ftp` en Airflow.
2. Identificar el “BD lógico” afectado (ej. `glm_pmi`, `IS001`, etc.).
3. Clasificar el fallo:
- FTP (login, path, listing)
- SQL (conexión, permisos, tabla)
- INSERT (schema/constraint)
- Kafka (broker/SSL/SASL)
- Webhook (endpoint caído / 4xx/5xx)
4. Ejecutar acciones según tipo (ver `08_TROUBLESHOOTING.md`).
## Operación programada del DAG
El DAG corre cada 2 horas (`0 */2 * * *`).
Si necesitas ejecución manual:
- Ejecuta el task `save_url_ftp` desde la UI de Airflow (Trigger DAG / Run Task).
## Indicadores recomendados (SLOs simples)
- **Frescura**: últimas URLs del FTP aparecen en SQL < 24 horas.
- **Cobertura**: por cada item, `A insertar` tiende a 0 tras una corrida exitosa.
- **Errores**: tasa de `FAILED` por item debe ser baja; si sube, revisar conectividad o cambios de naming en FTP.
## Mantenimiento preventivo
- Revisar que `FTP_BASE_PATH` (año) sea correcto cuando cambie el año.
- Verificar que las rutas/certificados SSL (si aplica) sean válidos dentro del contenedor.
- Confirmar que el naming en FTP siga el patrón esperado (contiene `_{YYYY}-{MM}` e `identity_id`).
+135
View File
@@ -0,0 +1,135 @@
# Troubleshooting
## 1) El DAG no aparece o falla al importarse
**Síntomas**
- Airflow muestra “ImportError” o el DAG no se lista.
**Causas comunes**
- La ruta `'/opt/airflow/dags/save_url_ftp'` no existe dentro del contenedor.
- `SaveUrl.py` no está en esa carpeta (o tiene otro nombre).
- Dependencias no instaladas en el entorno del worker (ej. `pyodbc`, `kafka`).
**Acciones**
- Verifica que existan los archivos:
- `/opt/airflow/dags/save_url_ftp/dag_save_url_ftp.py`
- `/opt/airflow/dags/save_url_ftp/SaveUrl.py`
- Revisa el scheduler logs por import errors.
## 2) Error conectando al FTP
**Síntomas**
- `❌ FTP | ...` en logs
- notificación por webhook con “Error leyendo FTP folder ...”
**Causas comunes**
- Credenciales incorrectas (`FTP_000oqta_USER` / `FTP_000oqta_PASSWORD`)
- Host incorrecto (`FTP_000oqta_SERVER`)
- Firewall / DNS / ruteo
- La carpeta del mes no existe (`<name_folder><MM>`)
**Acciones**
- Probar conectividad desde el mismo runtime (worker) hacia el host FTP.
- Confirmar que `FTP_BASE_PATH` + `code_folder` existe en el servidor.
- Verificar si el servidor cambió estructura para 2027/otro año (porque `FTP_BASE_PATH` incluye `2026`).
## 3) Error leyendo SQL Server
**Síntomas**
- `❌ SQL | ...` en logs
- webhook “Error leyendo URLs ...”
**Causas comunes**
- Driver ODBC faltante en el worker (`ODBC Driver 18 for SQL Server`)
- Credenciales inválidas o permisos insuficientes
- SQL Server inaccesible desde la red del worker
- Tabla `URL_FTP` inexistente o en otro schema/db esperado
**Acciones**
- Verificar instalación del driver ODBC en el contenedor/host.
- Validar que el usuario tenga `SELECT` sobre `URL_FTP`.
- Confirmar nombre de la base de datos: se toma de `INFORMATIONS` (ej. `glm_pmi`, `IS001`).
## 4) Error insertando (schema/constraints)
**Síntomas**
- `❌ INSERT | ...` en logs
- webhook “Error insertando URLs ...”
**Causas comunes**
- La tabla tiene columnas NOT NULL sin default que el dict no provee.
- Tipos incompatibles (ej. `Date` espera datetime pero recibe string).
- Restricción UNIQUE (por `IDcodigo`, `URL`, u otra combinación) que el código no contempla.
**Acciones**
- Revisar definición de `URL_FTP` y confirmar que acepta las columnas:
- `Folder`, `Date`, `IDcodigo`, `ObjectCode`, `TaskName`, `URL`
- Confirmar tipos: el código inserta strings.
- Si existe una columna “ExportDate”, el código NO la inserta (la excluye si aparece en dict).
## 5) No inserta nada, pero debería
**Síntomas**
- `A insertar: 0` repetidamente, aunque hay archivos nuevos en FTP.
**Causas comunes**
- `IDcodigo` se parsea mal (mismo valor para distintos archivos) por cambios de naming.
- La consulta SQL `Folder LIKE %code_folder%` trae más registros de los necesarios y “tapa” IDs.
- El FTP lista rutas que no cumplen el patrón y el parse genera IDs inesperados.
**Acciones**
- Tomar una URL real del FTP y revisar manualmente el parsing:
- ¿contiene `_{YYYY}-{MM}`?
- ¿contiene el `identity_id` correcto?
- ¿`IDcodigo` de 24 chars es correcto?
- Comparar un `IDcodigo` “nuevo” con los existentes en SQL.
## 6) Kafka no recibe logs
**Síntomas**
- Logs de Airflow muestran fallos de Kafka o no hay eventos en `executions_logs`.
**Causas comunes**
- `BOOTSTRAP_SERVERS_DOCKER` incorrecto para el runtime del worker.
- `KAFKA_USE_SSL=True` pero faltan variables de entorno `SECURITY_PROTOCOL`, `SASL_*`, `SSL_CAFILE`, etc.
- Certificados/rutas inválidas dentro del contenedor.
**Acciones**
- Validar conectividad al broker desde el contenedor/worker.
- Si SSL/SASL: asegurar que las variables están definidas como **env vars** del proceso.
## 7) Webhook no notifica
**Síntomas**
- Se ve el error en logs pero no llega alerta.
**Causas comunes**
- `WEBHOOK_URL_NOTIFICATION` no definido o es inválido.
- Endpoint requiere auth/cabeceras adicionales (el código no las envía).
- Restricciones de red del worker.
**Acciones**
- Probar un `POST` manual al webhook desde el mismo entorno.
- Revisar respuesta HTTP (4xx/5xx) en logs si se captura (actualmente no se valida el status code).
+58
View File
@@ -0,0 +1,58 @@
# Seguridad y manejo de secretos
## No versionar secretos
El repositorio incluye `.gitignore` para ignorar:
- `.env`
- `env/` (virtualenv)
Recomendación:
- Mantener **cualquier credencial** fuera del control de versiones.
## Dónde viven los secretos en este proyecto
El código en `SaveUrl.py` toma secretos principalmente desde:
- **Airflow Variables**:
- SQL: `DB_MSSQ_USER`, `DB_MSSQ_PASSWORD`, `DB_MSSQ_SERVER`
- FTP: `FTP_000oqta_USER`, `FTP_000oqta_PASSWORD`, `FTP_000oqta_SERVER`
- Webhook: `WEBHOOK_URL_NOTIFICATION`
- Kafka básico: `BOOTSTRAP_SERVERS_DOCKER`, `KAFKA_USE_SSL`
- **Variables de entorno del proceso** (solo cuando `KAFKA_USE_SSL=True`):
- `SECURITY_PROTOCOL`, `SASL_MECHANISM`
- `SASL_PLAIN_USERNAME`, `SASL_PLAIN_PASSWORD`
- `SSL_CAFILE`, `SSL_CHECK_HOSTNAME`
Implicación:
- Para Kafka con SSL/SASL, no basta con Airflow Variables: hay que configurar también **env vars** en el worker/scheduler (o usar un backend de secrets que las exponga como env vars).
## Recomendaciones de seguridad
- **Airflow Secrets Backend**: usar un backend (Vault/AWS Secrets Manager/Azure Key Vault/GCP Secret Manager) en vez de Variables planas.
- **Rotación**:
- FTP y SQL: rotación periódica y seguimiento de expiración.
- Kafka: rotación de credenciales SASL y CA si cambia.
- **Principio de mínimo privilegio**:
- SQL user: permisos mínimos necesarios sobre `URL_FTP` (SELECT/INSERT).
- FTP user: permisos mínimos para listar/leer rutas necesarias (NLST).
- **TLS**:
- si se habilita SSL en Kafka, asegurar que `SSL_CAFILE` apunte a un CA dentro del contenedor.
## Señales de filtración / exposición
Revisar que no exista:
- `.env` real en el repo
- credenciales hardcodeadas en archivos `.py`
- rutas de certificados locales de un desarrollador (ej. paths absolutos de Windows) en configuración de producción
## Checklist antes de producción
- Variables/secretos definidos en sistema seguro
- `.env` excluido y no presente en artefactos de build
- permisos SQL y FTP mínimos
- endpoints (Kafka/webhook) accesibles solo desde redes necesarias