#!/usr/bin/env python3 import argparse import sys from cluster_summary import summarize_clinic_cluster from common import ( CLINIC_METRICS_DATA_TYPE, ClinicApiPreflightRequiredError, ClinicLoginRequiredError, DEFAULT_BASE_URL, DEFAULT_CHROME_PROFILE, attach_wait_result, clinic_api_preflight_meta, clinic_cluster_meta, clinic_data_not_ready_message, clinic_data_timeout_message, clinic_login_state, clinic_session, discover_dashboard, parse_clinic_link, wait_for_clinic_data_ready_status, write_json, ) def build_login_state(login_state: dict) -> dict: return { "base_url": login_state["base_url"], "profile_name": login_state["profile_name"], "expires_at": login_state["expires_at"], } def build_source_payload(link: str, clinic_context: dict, ready: bool) -> dict: return { "kind": "clinic_link", "link": link, "base_url": clinic_context.get("base_url"), "org_id": clinic_context.get("org_id"), "cluster_id": clinic_context.get("cluster_id"), "window": { "from": clinic_context.get("from"), "to": clinic_context.get("to"), }, "ready": ready, } def build_pending_payload(link: str, login_state: dict, clinic_context: dict, data_status: dict) -> dict: return { "preflight_decision": {}, "login_state": build_login_state(login_state), "source": build_source_payload(link, clinic_context, ready=False), "clinic_context": clinic_context, "data_status": data_status, "access_warnings": [ clinic_data_not_ready_message( clinic_context, data_status, action="Clinic access", ) ], "dashboard_discovery": None, "cluster_meta": {}, "cluster_summary": {}, } def build_handoff_payload(link: str, clinic_context: dict, preflight_decision: dict) -> dict: return { "preflight_decision": preflight_decision, "login_state": {}, "source": build_source_payload(link, clinic_context, ready=False), "clinic_context": clinic_context, "data_status": {}, "access_warnings": [ "clinic-api metadata preflight found clusterType=cloud on this clinic.pingcap.com portal link, " "so analysis should hand off to the TiDB Cloud Clinic flow outside op-clinic-grafana-access." ], "dashboard_discovery": None, "cluster_meta": {}, "cluster_summary": {}, } def normalize_cluster_meta(cluster_meta: dict | None) -> dict: meta = cluster_meta or {} return { "id": meta.get("id"), "name": meta.get("name"), "version": meta.get("version"), "deployType": meta.get("deployType"), "deployTypeV2": meta.get("deployTypeV2"), "topology": meta.get("topology"), "status": meta.get("status"), } def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description="Collect normalized Clinic access evidence for one Clinic link using a reusable Chrome login session" ) parser.add_argument("link", help="Clinic portal link") parser.add_argument( "--base-url", default="", help="Optional Clinic base URL override. Defaults to the host in the Clinic link, then env CLINIC_BASE_URL.", ) parser.add_argument( "--profile", default=DEFAULT_CHROME_PROFILE, help="Chrome profile name. Use 'auto' to scan profiles for clinic.session_id " "(default: env CLINIC_CHROME_PROFILE or auto).", ) parser.add_argument("--dashboard-uid", default="", help="Optional Grafana dashboard uid for discovery") parser.add_argument("--dashboard-name", default="", help="Optional dashboard name substring for discovery") return parser def analyze_clinic_access( link: str, *, base_url: str = "", profile_name: str = DEFAULT_CHROME_PROFILE, dashboard_uid: str = "", dashboard_name: str = "", ) -> dict: clinic_context = parse_clinic_link(link) preflight_decision = clinic_api_preflight_meta(clinic_context) if bool(preflight_decision.get("handoff_to_tidb_cloud_clinic")): return build_handoff_payload(link, clinic_context, preflight_decision) effective_base_url = str(base_url or clinic_context.get("base_url") or DEFAULT_BASE_URL).rstrip("/") login_state = clinic_login_state(base_url=effective_base_url, profile_name=profile_name) session = clinic_session(base_url=effective_base_url, profile_name=profile_name) data_status, wait_result = wait_for_clinic_data_ready_status( session, clinic_context, data_type=CLINIC_METRICS_DATA_TYPE, ) data_status = attach_wait_result(data_status, wait_result) if wait_result.get("timed_out"): raise RuntimeError( clinic_data_timeout_message( clinic_context, data_status, action="Clinic access", waited_seconds=int(wait_result.get("waited_seconds") or 0), max_wait_seconds=int(wait_result.get("max_wait_seconds") or 0), ) ) if not data_status.get("ready"): payload = build_pending_payload(link, login_state, clinic_context, data_status) payload["preflight_decision"] = preflight_decision return payload discovery = discover_dashboard( link, dashboard_uid=dashboard_uid or None, dashboard_name=dashboard_name or None, base_url=effective_base_url, profile_name=profile_name, ) cluster_meta = clinic_cluster_meta(session, clinic_context) cluster_summary = summarize_clinic_cluster(session, clinic_context, cluster_meta) return { "preflight_decision": preflight_decision, "login_state": build_login_state(login_state), "source": build_source_payload(link, clinic_context, ready=True), "clinic_context": clinic_context, "data_status": data_status, "access_warnings": [], "dashboard_discovery": discovery, "cluster_meta": normalize_cluster_meta(cluster_meta), "cluster_summary": cluster_summary, } def main() -> int: parser = build_parser() args = parser.parse_args() try: payload = analyze_clinic_access( args.link, base_url=args.base_url, profile_name=args.profile, dashboard_uid=args.dashboard_uid, dashboard_name=args.dashboard_name, ) except (ClinicApiPreflightRequiredError, ClinicLoginRequiredError, RuntimeError, ValueError) as exc: print(exc, file=sys.stderr) return 1 write_json(payload) return 0 if __name__ == "__main__": raise SystemExit(main())