Files
cx-ui/cx-triage
Parham Monfared a039e0b5fd CX Triage: alert diagnosis over the CX-Tools collectors
Read-only triage for the Infrahub error alerts. Pulls the Prometheus alert
queue, re-checks each alert's condition against live state to separate real
work from noise, diagnoses it using the CX runbooks, and drafts the customer
comms with contacts resolved from Infrahub.

Findings from validating against production:
- "Suspected Rogue VM" fires on spare GPU capacity, not rogue VMs: In_Use_Gpus
  equals the physical count on 71 of 75 firing hosts, so the rule reduces to
  "this host has a free GPU". Verified against OpenStack on 10 hosts.
- "Exists in Infrahub but does not exist in OpenStack" matches every VM because
  openstack_nova_server_status returns no series; excluded as a rule defect.
- Prometheus activeAt is reset several times a day by dips in the Resources
  metric, so alert ages are recovered from ALERTS history instead.

Takes ~2,650 firing alerts down to ~20 that need a decision.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 06:48:34 +01:00

80 lines
2.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""CX Triage - diagnose Infrahub error alerts using the CX-Tools collectors.
Read-only: this tool queries Infrahub, OpenStack, InfraInsight and Prometheus,
and suggests what to do. It never changes platform state and never sends
customer comms.
"""
from __future__ import annotations
import argparse
import os
import sys
import webbrowser
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from triagelib import VERSION, cxbridge # noqa: E402
from triagelib.prometheus import DEFAULT_BASE, PrometheusClient, PrometheusError # noqa: E402
def preflight(prometheus_base: str) -> bool:
ok = True
print("Locating CX-Tools...")
try:
path = cxbridge.locate_cx_tools()
print(f" found: {path}")
except cxbridge.BridgeError as exc:
print(f" FAIL: {exc}", file=sys.stderr)
return False
# Done in the foreground so any 1Password prompt reaches the terminal
# rather than a background HTTP worker.
print("Loading API credentials from 1Password (CX-Tools loader)...")
try:
cxbridge.bootstrap()
print(" credentials loaded")
except cxbridge.BridgeError as exc:
print(f" FAIL: {exc}", file=sys.stderr)
ok = False
print(f"Checking Prometheus at {prometheus_base}...")
try:
print(f" reachable ({PrometheusClient(prometheus_base).describe_transport()})")
except PrometheusError as exc:
print(f" WARN: {exc}", file=sys.stderr)
print(" The app will still start; paste alerts manually until this is fixed.", file=sys.stderr)
return ok
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--host", default="127.0.0.1", help="bind address (default: localhost only)")
parser.add_argument("--port", type=int, default=8765, help="port (default: 8765)")
parser.add_argument("--prometheus", default=DEFAULT_BASE, help=f"Prometheus base URL (default: {DEFAULT_BASE})")
parser.add_argument("--no-open", action="store_true", help="do not open a browser on start")
parser.add_argument("--check", action="store_true", help="run preflight checks and exit")
parser.add_argument("--version", action="version", version=VERSION)
args = parser.parse_args()
if not preflight(args.prometheus):
return 1
if args.check:
print("preflight OK")
return 0
url = f"http://{args.host}:{args.port}"
if not args.no_open:
webbrowser.open(url)
from triagelib.server import serve
serve(host=args.host, port=args.port, prometheus_base=args.prometheus)
return 0
if __name__ == "__main__":
raise SystemExit(main())