feat(infra): auto-commit con 56 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"""Construye un MetabaseClient autenticado leyendo credenciales desde `pass`.
|
||||
|
||||
Elimina el patron inline repetido de "leer la credencial de Metabase del
|
||||
password store y montar un cliente autenticado", que hoy se reescribe a mano
|
||||
para dos instancias distintas:
|
||||
|
||||
- Aurgi: API-key (``metabase/aurgi-api-key``, una sola linea ``mb_...``) ->
|
||||
header ``X-API-KEY``.
|
||||
- Captacion: usuario/password (``captacion/metabase``, multi-linea: primera
|
||||
linea password, linea ``email:`` con el usuario) -> login via
|
||||
``POST /api/session``.
|
||||
|
||||
Compone tres funciones del registry: ``pass_get_secret`` (lee el secreto),
|
||||
``parse_metabase_secret`` (parser puro que distingue api_key vs session) y
|
||||
``metabase_auth`` / ``MetabaseClient`` (auth). No reimplementa ninguna.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
from infra.pass_get_secret import pass_get_secret
|
||||
from metabase.parse_metabase_secret import parse_metabase_secret
|
||||
from metabase.client import MetabaseClient, metabase_auth
|
||||
|
||||
# Tope de lineas a leer del secreto. pass_get_secret devuelve una linea por
|
||||
# llamada; leemos hasta este maximo o hasta "out of range" para reconstruir el
|
||||
# texto completo sin reimplementar el subproceso de pass.
|
||||
_MAX_SECRET_LINES = 16
|
||||
|
||||
|
||||
def metabase_client_from_pass(
|
||||
pass_key: str,
|
||||
base_url: str,
|
||||
mode: str = "auto",
|
||||
) -> MetabaseClient | dict:
|
||||
"""Lee credenciales de Metabase de `pass` y devuelve un cliente autenticado.
|
||||
|
||||
Args:
|
||||
pass_key: ruta del secreto en el password store (p.ej.
|
||||
``"metabase/aurgi-api-key"`` o ``"captacion/metabase"``).
|
||||
base_url: URL base de la instancia Metabase (p.ej.
|
||||
``"https://reports.autingo.es"``).
|
||||
mode: ``"api_key"``, ``"session"`` o ``"auto"`` (default). En ``auto`` se
|
||||
detecta el formato del secreto: una sola linea de clave -> api_key;
|
||||
multi-linea con email/usuario -> session.
|
||||
|
||||
Returns:
|
||||
``MetabaseClient`` autenticado en exito. En caso de fallo (sin lanzar):
|
||||
``{"status": "error", "error": str}`` para: secreto no encontrado en
|
||||
pass, formato no parseable, o fallo de autenticacion contra Metabase.
|
||||
|
||||
Example:
|
||||
>>> client = metabase_client_from_pass(
|
||||
... "metabase/aurgi-api-key", "https://reports.autingo.es", mode="api_key")
|
||||
>>> client.request("GET", "/api/user/current") # doctest: +SKIP
|
||||
"""
|
||||
secret_text = _read_secret_text(pass_key)
|
||||
if isinstance(secret_text, dict):
|
||||
return secret_text # error dict de pass
|
||||
|
||||
parsed = parse_metabase_secret(secret_text, mode=mode)
|
||||
if parsed["status"] != "ok":
|
||||
return {"status": "error", "error": parsed["error"]}
|
||||
|
||||
try:
|
||||
if parsed["mode"] == "api_key":
|
||||
# MetabaseClient detecta "mb_" -> usa header X-API-KEY.
|
||||
return MetabaseClient(base_url, parsed["api_key"])
|
||||
# mode == "session": login con email/password via POST /api/session.
|
||||
return metabase_auth(base_url, parsed["email"], parsed["password"])
|
||||
except Exception as exc: # noqa: BLE001 - cualquier fallo de red/auth se reporta
|
||||
return {"status": "error", "error": f"metabase auth failed: {exc}"}
|
||||
|
||||
|
||||
def _read_secret_text(pass_key: str) -> str | dict:
|
||||
"""Reconstruye el texto multi-linea del secreto via pass_get_secret.
|
||||
|
||||
Llama a pass_get_secret linea por linea (1-indexed) hasta agotar las lineas
|
||||
("line N out of range") o llegar al tope. Devuelve el texto unido por
|
||||
``\\n`` o un dict de error si la primera lectura falla (pass no instalado,
|
||||
entry inexistente, etc.).
|
||||
"""
|
||||
first = pass_get_secret(pass_key, line=1)
|
||||
if first["status"] != "ok":
|
||||
return {"status": "error", "error": first["error"]}
|
||||
|
||||
lines = [first["value"]]
|
||||
for n in range(2, _MAX_SECRET_LINES + 1):
|
||||
res = pass_get_secret(pass_key, line=n)
|
||||
if res["status"] != "ok":
|
||||
break # out of range = fin del secreto
|
||||
lines.append(res["value"])
|
||||
return "\n".join(lines)
|
||||
Reference in New Issue
Block a user