feat(infra): auto-commit con 56 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 14:22:55 +02:00
parent c1071a82b3
commit 32c7336bf6
56 changed files with 5307 additions and 100 deletions
@@ -0,0 +1,97 @@
"""Parsea el texto de un secreto de `pass` para credenciales de Metabase.
Distingue dos formatos sin tocar disco ni red (funcion pura):
- API-key: una sola linea con la clave (las API keys de Metabase empiezan por
``mb_``, p.ej. el secreto ``metabase/aurgi-api-key``).
- Sesion: multi-linea estilo ``captacion/metabase`` — la primera linea es la
contrasena y una linea posterior lleva el email/usuario con un prefijo
reconocible (``email:``, ``user:``, ``login:`` o ``username:``).
El caller decide el ``mode`` y este parser solo extrae los campos del texto.
"""
# Prefijos (case-insensitive) que identifican la linea del email/usuario en un
# secreto multi-linea de pass. Se prueban en este orden.
_EMAIL_PREFIXES = ("email:", "login:", "username:", "user:")
def parse_metabase_secret(secret_text: str, mode: str = "auto") -> dict:
"""Extrae credenciales de Metabase del texto crudo de un secreto de pass.
No ejecuta `pass` ni hace I/O: recibe el texto ya leido y lo interpreta.
Funcion pura y determinista, apta para tests unitarios.
Args:
secret_text: contenido completo del secreto (varias lineas separadas por
``\\n``). Por convencion de pass la primera linea es la
contrasena/clave; las siguientes son metadata.
mode: ``"api_key"``, ``"session"`` o ``"auto"`` (default). En ``auto`` se
detecta el formato: si hay una linea de email/usuario reconocible se
asume sesion; si no, se asume api_key (una sola linea de clave).
Returns:
Dict. Nunca lanza:
- api_key -> ``{"status": "ok", "mode": "api_key", "api_key": str}``
- session -> ``{"status": "ok", "mode": "session", "email": str,
"password": str}``
- error -> ``{"status": "error", "error": str}`` para texto vacio, modo
invalido, o session sin email/password localizables.
Example:
>>> parse_metabase_secret("mb_abc123")
{'status': 'ok', 'mode': 'api_key', 'api_key': 'mb_abc123'}
>>> parse_metabase_secret("hunter2\\nemail: a@b.com\\nurl: http://x")
{'status': 'ok', 'mode': 'session', 'email': 'a@b.com', 'password': 'hunter2'}
"""
if mode not in ("api_key", "session", "auto"):
return {"status": "error", "error": f"invalid mode {mode!r}"}
lines = secret_text.splitlines()
if not lines or not lines[0].strip():
return {"status": "error", "error": "empty secret"}
email = _find_email(lines)
if mode == "auto":
mode = "session" if email is not None else "api_key"
if mode == "api_key":
return {
"status": "ok",
"mode": "api_key",
"api_key": lines[0].strip(),
}
# mode == "session"
if email is None:
return {
"status": "error",
"error": (
"session secret without email/user line "
f"(expected one of {', '.join(_EMAIL_PREFIXES)})"
),
}
password = lines[0].strip()
if not password:
return {"status": "error", "error": "session secret without password"}
return {
"status": "ok",
"mode": "session",
"email": email,
"password": password,
}
def _find_email(lines: list[str]) -> str | None:
"""Devuelve el email/usuario de la primera linea con prefijo reconocido."""
for raw in lines[1:]:
low = raw.strip().lower()
for prefix in _EMAIL_PREFIXES:
if low.startswith(prefix):
# Conserva el valor original (no el lowercased) tras el prefijo.
value = raw.strip()[len(prefix):].strip()
if value:
return value
return None