#!/usr/bin/env python3 """Shared safe authentication and cluster discovery for Manual Invoice O11Y runners.""" from __future__ import annotations import datetime as dt import importlib.util import json import os import sys import time import urllib.error import urllib.request from pathlib import Path from types import ModuleType from typing import Any from runtime_config import load_runtime_config AUTH_URL = "https://www.ds.us-west-2.aws.observability.tidbcloud.com/client/auth" SUPPORTED_PROVIDERS = ("aws", "gcp", "alicloud") def write_private_json(path: Path, value: Any) -> None: path = path.expanduser().resolve() path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(value, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") os.chmod(path, 0o600) def request_json(request: urllib.request.Request, timeout: int) -> dict[str, Any]: with urllib.request.urlopen(request, timeout=timeout) as response: body = json.loads(response.read().decode("utf-8")) if not isinstance(body, dict): raise ValueError("O11Y response must be a JSON object.") return body def get_auth0_token( provider: str | None = None, timeout: int = 30, force_refresh: bool = False, ) -> tuple[str, str, str]: load_runtime_config() selected_provider = (provider or os.environ.get("O11Y_PROVIDER") or "aws").lower() if selected_provider not in SUPPORTED_PROVIDERS: raise ValueError( f"Unsupported O11Y provider {selected_provider!r}; expected aws, gcp, or alicloud." ) cached_token = os.environ.get("O11Y_AUTH0_TOKEN", "") try: cached_expires = int(os.environ.get("O11Y_AUTH0_TOKEN_EXPIRES", "0")) except ValueError: cached_expires = 0 if not force_refresh and cached_token and cached_expires > time.time() + 300: return cached_token, "cached", selected_provider github_pat = os.environ.get("GITHUB_PAT", "") if not github_pat: raise ValueError("GITHUB_PAT is not configured; run $manual-invoice configure.") payload = json.dumps( {"github_token": github_pat, "provider": selected_provider} ).encode("utf-8") request = urllib.request.Request( AUTH_URL, data=payload, headers={"Content-Type": "application/json", "User-Agent": "manual-invoice"}, method="POST", ) last_error = "unknown error" for attempt in range(3): try: body = request_json(request, timeout) token = str(body.get("auth0_token") or "") if not token: raise ValueError("O11Y authentication returned no auth0_token.") return token, "fresh", selected_provider except urllib.error.HTTPError as exc: last_error = f"HTTP {exc.code}" except (urllib.error.URLError, TimeoutError) as exc: last_error = type(exc).__name__ if attempt < 2: time.sleep(2**attempt) raise RuntimeError(f"O11Y authentication failed: {last_error}") def o11y_403_message(source: str, reauth_error: Exception | None = None) -> str: """Return a safe remediation message for an O11Y 403 after one retry.""" detail = "" if reauth_error is not None: detail = f" Re-authentication could not complete ({type(reauth_error).__name__})." return ( f"O11Y {source} returned HTTP 403 after one forced re-authentication retry." f"{detail} Connect to the Office network/Feilian VPN and try again;" " a 403 can indicate that the O11Y endpoint is not reachable from the current network." ) def load_nutshell_o11y() -> tuple[ModuleType, Path]: skills_root = Path( os.environ.get("O11Y_SKILLS_ROOT", Path.home() / ".codex" / "skills") ).expanduser() implementation = skills_root / "o11y-data" / "o11y-inspect.py" if not implementation.is_file(): raise ValueError( "The installed nutshell o11y-data implementation is missing; " "run $manual-invoice configure." ) spec = importlib.util.spec_from_file_location("manual_invoice_nutshell_o11y", implementation) if spec is None or spec.loader is None: raise ValueError(f"Cannot load O11Y implementation: {implementation}") module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module spec.loader.exec_module(module) return module, implementation.resolve() def load_nutshell_clinic() -> tuple[ModuleType, Path] | tuple[None, None]: skills_root = Path( os.environ.get("O11Y_SKILLS_ROOT", Path.home() / ".codex" / "skills") ).expanduser() implementation = skills_root / "clinic-api" / "scripts" / "clinic_api.py" if not implementation.is_file(): return None, None spec = importlib.util.spec_from_file_location( "manual_invoice_nutshell_clinic", implementation ) if spec is None or spec.loader is None: return None, None module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module spec.loader.exec_module(module) return module, implementation.resolve() def try_clinic_skill_context( cluster_id: str, ) -> tuple[dict[str, str] | None, Path | None]: """Use the installed Clinic Skill when its required credential is already available.""" try: module, implementation = load_nutshell_clinic() except Exception: return None, None if module is None or implementation is None: return None, None if not os.environ.get("CLINIC_API_KEY"): return None, implementation try: env_file = os.environ.get("ENV_FILE", os.devnull) api = module.ClinicAPI(env_file=env_file) payload = api.search_cluster_by_id(cluster_id, show_deleted=True) except Exception: return None, implementation if not isinstance(payload, dict) or payload.get("error"): return None, implementation item = next( ( entry for entry in payload.get("items", []) if str(entry.get("clusterID") or "") == str(cluster_id) ), None, ) if not item: return None, implementation provider = str( item.get("clusterProviderName") or item.get("provider") or item.get("vendor") or "" ).lower() provider = { "amazon web services": "aws", "google cloud": "gcp", "alibaba cloud": "alicloud", }.get(provider, provider) context = { "provider": provider, "region": str(item.get("clusterRegionName") or item.get("region") or ""), "deploy_type": str( item.get("clusterDeployType") or item.get("clusterDeployTypeV2") or item.get("deployType") or "" ).lower(), "tenant_id": str(item.get("tenantID") or ""), "project_id": str(item.get("projectID") or ""), } if not context["provider"] or not context["region"]: return None, implementation return context, implementation def resolve_cluster( cluster_id: str, expected_tenant_id: str | None = None, provider_hint: str | None = None, region_hint: str | None = None, deploy_type_hint: str | None = None, analysis_timestamp: int | None = None, timeout: int = 30, ) -> tuple[dict[str, Any], str]: load_runtime_config() clinic_context, clinic_implementation = try_clinic_skill_context(cluster_id) effective_provider = provider_hint or ( clinic_context.get("provider") if clinic_context else None ) effective_region = region_hint or ( clinic_context.get("region") if clinic_context else None ) token, authentication, auth_provider = get_auth0_token(effective_provider, timeout) module, implementation = load_nutshell_o11y() try: cluster = module.find_cluster( token, cluster_id, hint_region=effective_region, hint_vendor=effective_provider, analysis_ts=analysis_timestamp, ) except SystemExit as exc: raise RuntimeError( f"Cluster {cluster_id} was not found by Clinic or the bounded O11Y GS scan." ) from exc context = { "cluster_id": cluster_id, "tenant_id": str(cluster.get("tenant_id") or ""), "project_id": str(cluster.get("project_id") or ""), "provider": str(cluster.get("vendor") or ""), "region": str(cluster.get("region") or ""), "deploy_type": str(cluster.get("deploy_type") or ""), "biz_type": str(cluster.get("biz_type") or ""), "application_id": str(cluster.get("application_id") or ""), "parent_id": str(cluster.get("parent_id") or ""), "namespace": str(cluster.get("namespace") or ""), "app_o11y_id": str(cluster.get("o11y_id") or ""), "app_metrics_base": str(cluster.get("metrics_base") or ""), "infra_o11y_id": str(cluster.get("infra_o11y_id") or ""), "infra_metrics_base": str(cluster.get("infra_metrics_base") or ""), "keyspace_id": str(cluster.get("keyspace_id") or ""), "resolution_strategy": ( "clinic_api_skill_then_nutshell_gs_lookup" if clinic_context else "bounded_nutshell_gs_scan" ), "clinic_skill_available": clinic_implementation is not None, "clinic_skill_used": clinic_context is not None, "clinic_credential_configured": bool(os.environ.get("CLINIC_API_KEY")), "clinic_implementation": ( str(clinic_implementation) if clinic_implementation is not None else None ), "nutshell_implementation": str(implementation), "authentication": authentication, "authentication_provider": auth_provider, "resolved_at_utc": dt.datetime.now(dt.timezone.utc).isoformat(), } if not context["provider"] or not context["region"] or not context["app_o11y_id"]: raise RuntimeError("O11Y discovery returned incomplete provider, region, or metrics target.") validations = { "tenant_match": ( None if not expected_tenant_id else context["tenant_id"] == str(expected_tenant_id) ), "provider_match": ( None if not provider_hint else context["provider"].lower() == provider_hint.lower() ), "region_match": ( None if not region_hint else context["region"] == region_hint ), "deploy_type_match": ( None if not deploy_type_hint else context["deploy_type"].lower() == deploy_type_hint.lower() ), } context["validation"] = validations failed = [name for name, value in validations.items() if value is False] if failed: raise ValueError( "Resolved O11Y context does not match supplied constraints: " + ", ".join(failed) ) return context, token