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>
This commit is contained in:
123
infra.py
Normal file
123
infra.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user