"""CIS Engine routes — version management, check-update, update, health check.""" import re import time from pathlib import Path from datetime import datetime from fastapi import APIRouter, HTTPException, Depends from config import VERSION, _START_TIME, log from database import db from auth.jwt_auth import current_user, require, _audit router = APIRouter() # ── Constants ──────────────────────────────────────────────────────────────── CIS_REPORTS_PATH = Path("/app/cis_reports.py") CIS_GITHUB_RAW = "https://raw.githubusercontent.com/oci-landing-zones/oci-cis-landingzone-quickstart/main/scripts/cis_reports.py" CIS_PATCHES = [ {"name": "region_none_check_1", "original": "elif region.region_name in self.__regions_to_run_in or self.__run_in_all_regions:", "patched": "elif self.__run_in_all_regions or (self.__regions_to_run_in and region.region_name in self.__regions_to_run_in):"}, {"name": "region_none_check_2", "original": "if self.__home_region not in self.__regions_to_run_in:", "patched": "if not self.__run_in_all_regions and self.__regions_to_run_in and self.__home_region not in self.__regions_to_run_in:"}, ] # ── Helpers ────────────────────────────────────────────────────────────────── def _read_cis_version(content: str = None) -> dict: if content is None: if not CIS_REPORTS_PATH.exists(): return {"version": "unknown", "date": "unknown"} content = CIS_REPORTS_PATH.read_text(encoding="utf-8", errors="replace") ver = re.search(r'RELEASE_VERSION\s*=\s*["\']([^"\']+)["\']', content) dt = re.search(r'UPDATED_DATE\s*=\s*["\']([^"\']+)["\']', content) return {"version": ver.group(1) if ver else "unknown", "date": dt.group(1) if dt else "unknown"} # ── Endpoints ──────────────────────────────────────────────────────────────── @router.get("/api/cis-engine/version") async def cis_engine_version(u=Depends(current_user)): info = _read_cis_version() return {"version": info["version"], "updated_date": info["date"], "file_path": str(CIS_REPORTS_PATH)} @router.get("/api/cis-engine/check-update") async def cis_engine_check_update(u=Depends(require("admin"))): import requests as req local = _read_cis_version() try: resp = req.get(CIS_GITHUB_RAW, timeout=30) resp.raise_for_status() remote = _read_cis_version(resp.text) except Exception as e: raise HTTPException(502, f"Failed to check GitHub: {str(e)[:300]}") return { "local_version": local["version"], "local_date": local["date"], "remote_version": remote["version"], "remote_date": remote["date"], "update_available": remote["version"] != local["version"] } @router.post("/api/cis-engine/update") async def cis_engine_update(u=Depends(require("admin"))): import requests as req old = _read_cis_version() try: resp = req.get(CIS_GITHUB_RAW, timeout=60) resp.raise_for_status() content = resp.text except Exception as e: raise HTTPException(502, f"Failed to download from GitHub: {str(e)[:300]}") new = _read_cis_version(content) # Apply patches patches_applied = [] for p in CIS_PATCHES: if p["original"] in content: content = content.replace(p["original"], p["patched"]) patches_applied.append(p["name"]) CIS_REPORTS_PATH.write_text(content, encoding="utf-8") # Save version info with db() as c: c.execute("INSERT INTO app_settings (key,value,updated_at) VALUES ('cis_engine_version',?,datetime('now')) ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at", (new["version"],)) log.info(f"CIS engine updated: {old['version']} → {new['version']}, patches: {patches_applied}") _audit(u["id"], u["username"], "cis_engine_update", None, f"{old['version']} → {new['version']}") return {"ok": True, "old_version": old["version"], "new_version": new["version"], "patches_applied": patches_applied} @router.get("/api/health") async def health(): db_ok = False try: with db() as c: c.execute("SELECT 1") db_ok = True except Exception: pass return { "status": "ok", "version": VERSION, "uptime_seconds": int(time.time() - _START_TIME), "db_ok": db_ok, "ts": datetime.utcnow().isoformat(), }