Files
infra-tool/infra.py
kappa 5e59261f63 Add infra-tool: infrastructure registry with Incus container deployment
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>
2026-03-03 09:13:43 +09:00

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()