Service registry & discovery system that aggregates infrastructure metadata from Incus, K8s, APISIX, and BunnyCDN into NocoDB. Includes FastAPI HTTP API, systemd timer for 15-min auto-sync, and dual-mode collectors (REST API for container deployment, CLI/SSH fallback for local use). Deployed to jp1:infra-tool with Tailscale socket proxy for host network visibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
124 lines
4.0 KiB
Python
124 lines
4.0 KiB
Python
"""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()
|