"""infra — Infrastructure service registry & discovery CLI.""" from __future__ import annotations import click import requests import config import nocodb_client import sync as sync_mod import lookup as lookup_mod @click.group() def cli(): """Infrastructure service registry & discovery.""" pass # ── sync ──────────────────────────────────────────────────────────── @cli.command() @click.option("--source", type=click.Choice(sync_mod.SYNC_ORDER), help="Sync specific source only") @click.option("--dry-run", is_flag=True, help="Preview without writing to NocoDB") def sync(source: str | None, dry_run: bool): """Scan infrastructure and sync to NocoDB.""" results = sync_mod.sync_all(dry_run=dry_run, source=source) click.echo("\n--- Summary ---") for src, stats in results.items(): click.echo(f" {src}: {stats}") # ── lookup ────────────────────────────────────────────────────────── @cli.command("lookup") @click.argument("domain") def lookup_cmd(domain: str): """Trace the full infra path for a domain.""" click.echo(f"Domain: {domain}") lookup_mod.lookup(domain) # ── list ──────────────────────────────────────────────────────────── @cli.command("list") @click.argument("table", type=click.Choice(["services", "routes", "containers", "zones"])) @click.option("--where", default="", help="NocoDB where filter") def list_cmd(table: str, where: str): """List rows from a NocoDB table.""" table_map = { "services": "infra_services", "routes": "infra_routes", "containers": "infra_containers", "zones": "infra_cdn_zones", } rows = nocodb_client.list_rows(table_map[table], where=where) if not rows: click.echo("No rows found.") return for row in rows: title = row.get("Title", "?") status = row.get("status", row.get("last_synced", "")) click.echo(f" {title} [{status}]") click.echo(f"\nTotal: {len(rows)}") # ── status ────────────────────────────────────────────────────────── @cli.command() def status(): """Check connectivity to NocoDB, APISIX, and Vault.""" # NocoDB try: token = config.nocodb_token() resp = requests.get( f"{config.NOCODB_URL}/api/v2/meta/bases/", headers={"xc-token": token}, timeout=5, ) resp.raise_for_status() click.echo(f" NocoDB: OK ({config.NOCODB_URL})") except Exception as e: click.echo(f" NocoDB: FAIL ({e})") # APISIX try: resp = requests.get( f"{config.APISIX_ADMIN_URL}/apisix/admin/routes", headers={"X-API-KEY": config.apisix_admin_key()}, timeout=5, ) resp.raise_for_status() n = len(resp.json().get("list", [])) click.echo(f" APISIX: OK ({n} routes)") except Exception as e: click.echo(f" APISIX: FAIL ({e})") # Vault try: client = config._vault_client() if client and client.is_authenticated(): click.echo(" Vault: OK") else: click.echo(" Vault: NOT CONNECTED") except Exception as e: click.echo(f" Vault: FAIL ({e})") # BunnyCDN try: resp = requests.get( "https://api.bunny.net/pullzone", headers={"AccessKey": config.bunny_api_key()}, timeout=5, ) resp.raise_for_status() n = len(resp.json()) click.echo(f" BunnyCDN: OK ({n} zones)") except Exception as e: click.echo(f" BunnyCDN: FAIL ({e})") if __name__ == "__main__": cli()