| |
| from __future__ import annotations |
|
|
| import argparse |
| import json |
| import os |
| import sys |
| from pathlib import Path |
| from typing import Any, Dict, List |
|
|
| REPO_ROOT = Path(__file__).resolve().parents[2] |
| BACKEND_DIR = REPO_ROOT / "backend" |
| if str(BACKEND_DIR) not in sys.path: |
| sys.path.insert(0, str(BACKEND_DIR)) |
|
|
| from app.auth import load_auth_settings |
|
|
|
|
| def _is_placeholder_key(value: str) -> bool: |
| v = str(value or "").strip() |
| if not v: |
| return True |
| vu = v.upper() |
| return vu.startswith("YOUR_KEY") or vu in {"<YOUR_OPENAI_API_KEY>", "YOUR_OPENAI_API_KEY", "REPLACE_ME"} |
|
|
|
|
| def _resolve_router_rag_paths() -> Dict[str, Path]: |
| data_dir = Path(str(os.getenv("ROUTER_RAG_DATA_DIR", "") or "").strip() or (REPO_ROOT / "_RAG_Ready_KB_Organized")) |
| chunks = Path(str(os.getenv("ROUTER_RAG_CHUNKS_PATH", "") or "").strip() or (data_dir / "04_ingestion" / "rag_ingestion_chunks.jsonl")) |
| manifest = Path(str(os.getenv("ROUTER_RAG_MANIFEST_PATH", "") or "").strip() or (data_dir / "03_manifests" / "rag_manifest_organized.csv")) |
| return {"data_dir": data_dir, "chunks": chunks, "manifest": manifest} |
|
|
|
|
| def _check_path(path: Path, *, file_required: bool = True) -> Dict[str, Any]: |
| exists = path.exists() |
| ok = exists and (path.is_file() if file_required else path.is_dir()) |
| return {"path": str(path), "exists": exists, "ok": ok} |
|
|
|
|
| def _build_report(args: argparse.Namespace) -> Dict[str, Any]: |
| checks: Dict[str, Any] = {} |
| failures: List[str] = [] |
| warnings: List[str] = [] |
|
|
| openai_key = str(os.getenv("OPENAI_API_KEY", "") or "").strip() |
| openai_ok = bool(openai_key) and not _is_placeholder_key(openai_key) |
| checks["openai"] = { |
| "required": bool(args.require_openai), |
| "present": bool(openai_key), |
| "placeholder": _is_placeholder_key(openai_key) if openai_key else True, |
| "ok": openai_ok or (not args.require_openai), |
| } |
| if args.require_openai and not openai_ok: |
| failures.append("OPENAI_API_KEY is missing or placeholder.") |
|
|
| auth = load_auth_settings() |
| checks["auth"] = { |
| "required": bool(auth.required), |
| "enabled": bool(auth.enabled), |
| "config_error": auth.config_error, |
| "config_details": list(auth.config_details), |
| "config_warnings": list(auth.config_warnings), |
| "ok": (not auth.required) or bool(auth.enabled), |
| } |
| if auth.required and not auth.enabled: |
| failures.append(auth.config_error or "Auth is required but Auth0 config is invalid.") |
| if auth.config_warnings: |
| warnings.extend(list(auth.config_warnings)) |
|
|
| repo_assets = { |
| "routers_eos_eol_by_sku.csv": REPO_ROOT / "routers_eos_eol_by_sku.csv", |
| "feb2026routers.csv": REPO_ROOT / "feb2026routers.csv", |
| "FAQ_master_updated.csv": REPO_ROOT / "docs" / "faq" / "FAQ_master_updated.csv", |
| } |
| checks["required_assets"] = {name: _check_path(path, file_required=True) for name, path in repo_assets.items()} |
| for name, data in checks["required_assets"].items(): |
| if not bool(data.get("ok")): |
| failures.append(f"Missing required asset: {name}") |
|
|
| rag = _resolve_router_rag_paths() |
| checks["router_rag"] = { |
| "data_dir": _check_path(rag["data_dir"], file_required=False), |
| "chunks": _check_path(rag["chunks"], file_required=True), |
| "manifest": _check_path(rag["manifest"], file_required=True), |
| "strict": bool(args.fail_on_missing_router_rag), |
| } |
| if args.fail_on_missing_router_rag: |
| if not checks["router_rag"]["chunks"]["ok"]: |
| failures.append("Router RAG chunks file is missing.") |
| if not checks["router_rag"]["manifest"]["ok"]: |
| failures.append("Router RAG manifest file is missing.") |
| else: |
| if not checks["router_rag"]["chunks"]["ok"] or not checks["router_rag"]["manifest"]["ok"]: |
| warnings.append("Router RAG ingestion artifacts are missing; router-doc retrieval may be degraded.") |
|
|
| return { |
| "ok": not failures, |
| "failures": failures, |
| "warnings": warnings, |
| "checks": checks, |
| } |
|
|
|
|
| def main() -> int: |
| parser = argparse.ArgumentParser(description="Preflight environment/configuration checks for CI and release gates.") |
| parser.add_argument("--require-openai", action="store_true", help="Fail if OPENAI_API_KEY is missing/placeholder.") |
| parser.add_argument( |
| "--fail-on-missing-router-rag", |
| action="store_true", |
| help="Fail if router RAG manifest/chunks files are missing.", |
| ) |
| parser.add_argument("--out", default="", help="Optional output JSON path.") |
| args = parser.parse_args() |
|
|
| report = _build_report(args) |
| payload = json.dumps(report, indent=2) |
| if args.out: |
| out_path = Path(args.out) |
| out_path.parent.mkdir(parents=True, exist_ok=True) |
| out_path.write_text(payload + "\n", encoding="utf-8") |
| print(f"Wrote preflight report: {out_path}") |
| print(payload) |
| return 0 if report.get("ok") else 2 |
|
|
|
|
| if __name__ == "__main__": |
| raise SystemExit(main()) |
|
|