Files
portfolio-hub/app/cli/commands/health.py
T

117 lines
4.8 KiB
Python

"""Health-related CLI commands."""
from __future__ import annotations
import typer
from app.cli.core.utils import print_json
from app.db.migrations import (
bootstrap_or_upgrade_to_head,
current_revision,
head_revision,
)
from app.repositories.factory import build_portfolio_db_config
from app.repositories.postgres import create_database_if_missing, database_exists
def initialize_database(*, settings, maintenance_db: str = "postgres") -> dict[str, object]:
config = build_portfolio_db_config(settings)
existed_before = database_exists(config, maintenance_db=maintenance_db)
created = False
if not existed_before:
created = create_database_if_missing(config, maintenance_db=maintenance_db)
bootstrap_or_upgrade_to_head(settings)
return {
"database": config.dbname,
"host": config.host,
"port": config.port,
"created": created,
"schema_initialized": True,
"migration_applied": "head",
}
def register_health_commands(app: typer.Typer) -> None:
@app.command("init-db")
def init_db(
ctx: typer.Context,
maintenance_db: str = typer.Option("postgres", help="Database to connect to for CREATE DATABASE."),
) -> None:
app_context = ctx.obj
try:
result = initialize_database(settings=app_context.settings, maintenance_db=maintenance_db)
except Exception as exc:
app_context.console.print(f"init-db failed: {exc}")
raise typer.Exit(code=1) from exc
else:
if app_context.output_format == "json":
print_json(app_context.console, result)
else:
app_context.console.print(f"database: {result['database']}")
app_context.console.print(f"host: {result['host']}")
app_context.console.print(f"port: {result['port']}")
app_context.console.print(f"created: {result['created']}")
app_context.console.print("schema_initialized: True")
app_context.console.print("migration_applied: head")
@app.command("migrate-db")
def migrate_db(ctx: typer.Context) -> None:
app_context = ctx.obj
try:
bootstrap_or_upgrade_to_head(app_context.settings)
result = {
"database": app_context.settings.active_db_name,
"host": app_context.settings.active_db_host,
"port": app_context.settings.active_db_port,
"migration_applied": "head",
}
except Exception as exc:
app_context.console.print(f"migrate-db failed: {exc}")
raise typer.Exit(code=1) from exc
else:
if app_context.output_format == "json":
print_json(app_context.console, result)
else:
app_context.console.print(f"database: {result['database']}")
app_context.console.print(f"host: {result['host']}")
app_context.console.print(f"port: {result['port']}")
app_context.console.print("migration_applied: head")
@app.command("health-check")
def health_check(ctx: typer.Context) -> None:
app_context = ctx.obj
repository = app_context.repository_factory(app_context.settings)
try:
health = {
"app_name": app_context.settings.app_name,
"environment": app_context.settings.environment,
"db_enabled": app_context.settings.db_enabled,
"active_db_host": app_context.settings.active_db_host,
"active_db_port": app_context.settings.active_db_port,
"repository": repository.__class__.__name__,
}
if hasattr(repository, "ping"):
repository.ping()
if app_context.settings.db_enabled:
database_revision = current_revision(app_context.settings)
expected_revision = head_revision(app_context.settings)
health["database_revision"] = database_revision
health["expected_revision"] = expected_revision
if database_revision != expected_revision:
raise RuntimeError(
f"database revision {database_revision!r} is not packaged head {expected_revision!r}"
)
except Exception as exc:
app_context.console.print(f"health-check failed: {exc}")
raise typer.Exit(code=1) from exc
else:
if app_context.output_format == "json":
print_json(app_context.console, health)
else:
for key, value in health.items():
app_context.console.print(f"{key}: {value}")
finally:
if callable(getattr(type(repository), "close", None)):
repository.close()