"""Pipeline: registra la operations.db de una app en Metabase. Compone: metabase_auth + metabase_list_databases + metabase_add_database. Requiere que el contenedor Metabase tenga montado el directorio de la app como volumen RW para que SQLite pueda crear journal files. Uso: python metabase_add_ops_db.py python metabase_add_ops_db.py docker_tui python metabase_add_ops_db.py --list """ import argparse import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from metabase.client import metabase_auth from metabase import metabase_list_databases, metabase_add_database METABASE_URL = os.environ.get("METABASE_URL", "http://localhost:3000") EMAIL = os.environ.get("METABASE_ADMIN_EMAIL", "admin@fnregistry.local") PASSWORD = os.environ.get("METABASE_ADMIN_PASSWORD", "FnRegistry2024!") CONTAINER_DATA_PREFIX = "/data/ops-" def find_apps(registry_root: str) -> list[str]: """Lista apps que tienen operations.db.""" apps_dir = os.path.join(registry_root, "apps") if not os.path.isdir(apps_dir): return [] return sorted( d for d in os.listdir(apps_dir) if os.path.isfile(os.path.join(apps_dir, d, "operations.db")) ) def db_name_for_app(app_name: str) -> str: return f"ops-{app_name.replace('_', '-')}" def container_path_for_app(app_name: str) -> str: return f"{CONTAINER_DATA_PREFIX}{app_name.replace('_', '-')}/operations.db" def main(): parser = argparse.ArgumentParser(description="Registra operations.db de una app en Metabase") parser.add_argument("app_name", nargs="?", help="Nombre de la app (directorio en apps/)") parser.add_argument("--list", action="store_true", help="Lista apps disponibles y su estado en Metabase") args = parser.parse_args() registry_root = os.environ.get( "FN_REGISTRY_ROOT", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")), ) if args.list: client = metabase_auth(METABASE_URL, EMAIL, PASSWORD) dbs = metabase_list_databases(client) db_names = {d["name"] for d in dbs} apps = find_apps(registry_root) print(f"Apps con operations.db ({len(apps)}):\n") for app in apps: name = db_name_for_app(app) status = "registrada" if name in db_names else "NO registrada" print(f" {app:30s} -> {name:30s} [{status}]") client.close() return if not args.app_name: parser.error("Se requiere app_name o --list") app_name = args.app_name ops_path = os.path.join(registry_root, "apps", app_name, "operations.db") if not os.path.isfile(ops_path): print(f"ERROR: No existe {ops_path}") print(f"Apps disponibles: {', '.join(find_apps(registry_root))}") sys.exit(1) name = db_name_for_app(app_name) container_path = container_path_for_app(app_name) print(f"Registrando {app_name} en Metabase...") print(f" Nombre: {name}") print(f" Path: {container_path}") client = metabase_auth(METABASE_URL, EMAIL, PASSWORD) # Verificar si ya existe dbs = metabase_list_databases(client) for db in dbs: if db["name"] == name: print(f" Ya existe con id={db['id']}, nada que hacer.") client.close() return # Registrar result = metabase_add_database(client, name, "sqlite", {"db": container_path}) db_id = result["id"] print(f" Registrada con id={db_id}") # Recordar: el contenedor necesita el volumen montado print(f"\nIMPORTANTE: El contenedor Metabase debe tener montado:") print(f" -v {os.path.join(registry_root, 'apps', app_name)}:{CONTAINER_DATA_PREFIX}{app_name.replace('_', '-')}") print(f"\nSi el mount no existe, hay que recrear el contenedor.") print(f"\nDatabase disponible en: {METABASE_URL}/question#new?database={db_id}") client.close() if __name__ == "__main__": main()