"""Pipeline: arregla permisos de SQLite en el contenedor Metabase. Compone: metabase_auth + metabase_list_databases (para verificar). SQLite necesita escribir journal/WAL files en el mismo directorio que la BD. El contenedor Metabase corre Java como UID 2000 (usuario metabase). Si los directorios o archivos no son escribibles por ese UID, Metabase devuelve SQLITE_READONLY_DIRECTORY. Este pipeline: 1. Detecta el contenedor Metabase 2. Encuentra todos los directorios /data/ que contienen .db 3. Hace chmod 777 en directorios y chmod 666 en archivos .db 4. Verifica que Metabase puede leer cada database Uso: python metabase_fix_permissions.py python metabase_fix_permissions.py --container fn_registry-metabase """ import argparse import os import subprocess 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_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!") DEFAULT_CONTAINER = "fn_registry-metabase" def docker_exec(container: str, cmd: list[str], user: str = "root") -> str: """Ejecuta comando dentro del contenedor.""" result = subprocess.run( ["docker", "exec", "-u", user, container] + cmd, capture_output=True, text=True, ) return result.stdout.strip() def fix_container_permissions(container: str) -> list[str]: """Arregla permisos de todos los .db en /data/ del contenedor. Returns: Lista de paths arreglados. """ # Encontrar todos los .db bajo /data/ find_output = docker_exec(container, ["find", "/data", "-name", "*.db", "-type", "f"]) if not find_output: print(" No se encontraron archivos .db en /data/") return [] db_files = [f.strip() for f in find_output.splitlines() if f.strip()] fixed = [] for db_path in db_files: db_dir = os.path.dirname(db_path) # Fix directorio: 777 para que UID 2000 pueda crear journal docker_exec(container, ["chmod", "777", db_dir]) # Fix archivo: 666 para que UID 2000 pueda leer/escribir docker_exec(container, ["chmod", "666", db_path]) # Fix WAL/SHM si existen for suffix in ["-wal", "-shm", "-journal"]: wal_path = db_path + suffix docker_exec(container, ["chmod", "666", wal_path]) fixed.append(db_path) print(f" Fixed: {db_path} (dir: {db_dir})") return fixed def verify_databases(client) -> bool: """Verifica que todas las databases SQLite responden.""" import httpx dbs = metabase_list_databases(client) all_ok = True for db in dbs: if db.get("engine") != "sqlite": continue name = db["name"] db_id = db["id"] try: resp = client._http.request("POST", "/api/dataset", json={ "database": db_id, "type": "native", "native": {"query": "SELECT 1"}, }, extensions={"timeout": {"read": 60, "write": 10, "connect": 10, "pool": 10}}) if resp.status_code in (200, 202): print(f" OK: {name} (id={db_id})") else: error = resp.text[:150] print(f" FAIL: {name} (id={db_id}) -> {error}") all_ok = False except httpx.ReadTimeout: print(f" TIMEOUT: {name} (id={db_id}) -> Metabase aun sincronizando, reintentar en unos segundos") all_ok = False return all_ok def main(): parser = argparse.ArgumentParser(description="Arregla permisos SQLite en contenedor Metabase") parser.add_argument("--container", default=DEFAULT_CONTAINER, help="Nombre del contenedor Metabase") args = parser.parse_args() container = args.container # Verificar que el contenedor existe result = subprocess.run( ["docker", "inspect", container, "--format", "{{.State.Running}}"], capture_output=True, text=True, ) if result.returncode != 0 or result.stdout.strip() != "true": print(f"ERROR: Contenedor '{container}' no esta corriendo.") sys.exit(1) print(f"Contenedor: {container}") print(f"\n1. Arreglando permisos en /data/...") fixed = fix_container_permissions(container) if not fixed: print(" Nada que arreglar.") return print(f"\n2. Verificando databases en Metabase...") client = metabase_auth(METABASE_URL, EMAIL, PASSWORD) ok = verify_databases(client) client.close() if ok: print(f"\nTodas las databases responden correctamente.") else: print(f"\nAlgunas databases siguen fallando. Revisar logs del contenedor:") print(f" docker logs {container} --tail 50") if __name__ == "__main__": main()