feat: metabase_setup Python, fix list_databases, volumen Docker en init_metabase

Nueva función metabase_setup para setup inicial via API. Fix list_databases
que no extraía data del response wrapper. Pipeline init_metabase soporta
--mb-volumes para montar SQLite como volumen con fix de permisos automático.
Añadido .env a gitignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-28 23:23:20 +01:00
parent ebbc3bfdab
commit 2e5bdacdcf
12 changed files with 821 additions and 8 deletions
+9 -4
View File
@@ -30,13 +30,17 @@ file_path: "functions/pipelines/init_metabase/main.go"
## Ejemplo
```bash
# Básico
go run functions/pipelines/init_metabase/main.go \
--project analytics \
--metabase-port 3000 \
--pg-port 5432 \
--pg-user metabase \
--pg-password metabase \
--pg-database metabase
--pg-password metabase
# Con volume para registry.db (detecta cambios en vivo)
go run functions/pipelines/init_metabase/main.go \
--project fn_registry \
--mb-volumes "/home/lucas/fn_registry/registry.db:/data/registry.db"
```
Salida JSON:
@@ -60,7 +64,8 @@ El pipeline orquesta 5 pasos secuenciales:
2. **Pull** — descarga `postgres:16` y `metabase/metabase:latest`
3. **Postgres** — inicia con volume persistente (named volume por defecto o bind mount con `--pg-volume`)
4. **Health check** — retry exponencial (hasta ~34 min) con `pg_isready` dentro del contenedor
5. **Metabase** — conecta a Postgres via red interna, expone en puerto configurable
5. **Metabase** — conecta a Postgres via red interna, expone en puerto configurable. Con `--mb-volumes` monta volumes adicionales (ej: registry.db para SQLite)
6. **Permisos** — ajusta ownership de directorios montados para el usuario `metabase` (UID 2000) dentro del contenedor
Reutiliza conceptualmente `docker_create_network`, `docker_pull_image`, `docker_run_container`, `docker_inspect_container` y `retry_with_backoff`, reimplementadas inline por ser un ejecutable independiente.
+33 -4
View File
@@ -28,7 +28,8 @@ func main() {
pgUser := flag.String("pg-user", "metabase", "Usuario Postgres")
pgPass := flag.String("pg-password", "metabase", "Password Postgres")
pgDB := flag.String("pg-database", "metabase", "Base de datos Postgres")
pgVolume := flag.String("pg-volume", "", "Path host para persistencia (default: docker named volume)")
pgVolume := flag.String("pg-volume", "", "Path host para persistencia Postgres (default: docker named volume)")
mbVolumes := flag.String("mb-volumes", "", "Volumes adicionales para Metabase, separados por coma (ej: /host/path:/container/path,/otro:/dest)")
flag.Parse()
if *project == "" {
@@ -37,7 +38,12 @@ func main() {
os.Exit(1)
}
result, err := initMetabase(*project, *mbPort, *pgPort, *pgUser, *pgPass, *pgDB, *pgVolume)
var extraVolumes []string
if *mbVolumes != "" {
extraVolumes = strings.Split(*mbVolumes, ",")
}
result, err := initMetabase(*project, *mbPort, *pgPort, *pgUser, *pgPass, *pgDB, *pgVolume, extraVolumes)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
@@ -48,7 +54,7 @@ func main() {
enc.Encode(result)
}
func initMetabase(project, mbPort, pgPort, pgUser, pgPass, pgDB, pgVolume string) (*MetabaseResult, error) {
func initMetabase(project, mbPort, pgPort, pgUser, pgPass, pgDB, pgVolume string, mbExtraVolumes []string) (*MetabaseResult, error) {
networkName := project + "-net"
pgName := project + "-postgres"
mbName := project + "-metabase"
@@ -118,13 +124,36 @@ func initMetabase(project, mbPort, pgPort, pgUser, pgPass, pgDB, pgVolume string
"-e", "MB_DB_USER=" + pgUser,
"-e", "MB_DB_PASS=" + pgPass,
"-e", "MB_DB_HOST=" + pgName,
"metabase/metabase:latest",
}
for _, v := range mbExtraVolumes {
mbArgs = append(mbArgs, "-v", strings.TrimSpace(v))
}
mbArgs = append(mbArgs, "metabase/metabase:latest")
mbID, err := dockerCmd(mbArgs...)
if err != nil {
return nil, fmt.Errorf("starting metabase: %w", err)
}
// 6. Fix permisos para volumes SQLite — Metabase corre como UID 2000
for _, v := range mbExtraVolumes {
parts := strings.SplitN(strings.TrimSpace(v), ":", 2)
if len(parts) < 2 {
continue
}
destPath := parts[1]
// Si es un archivo, fix el directorio padre; si es directorio, fix directo
dir := destPath
if strings.Contains(destPath, ".") {
// Probablemente un archivo, usar dirname
idx := strings.LastIndex(destPath, "/")
if idx > 0 {
dir = destPath[:idx]
}
}
fmt.Fprintf(os.Stderr, " Fijando permisos de %s para usuario metabase...\n", dir)
dockerCmd("exec", "-u", "root", mbName, "chown", "metabase:metabase", dir)
}
mbURL := fmt.Sprintf("http://localhost:%s", mbPort)
fmt.Fprintf(os.Stderr, "\nStack listo. Metabase disponible en %s\n", mbURL)