Fase 0 complete: extract all business logic, auth, database, and 176 endpoints from monolithic app.py into dedicated modules. Structure: - app.py: FastAPI hub (CORS, routers, startup/shutdown) - models.py: 13 Pydantic request models - utils.py: shared utilities (embed status, upload validation, process registries) - config.py: constants, env vars, model catalogs - auth/: crypto, jwt_auth, oidc, rate_limit - database/: db(), init_db(), schema DDL - services/: genai, compliance, chat, terraform, embeddings, cis_reports, mcp - routes/: 13 APIRouter modules (auth, users, oci_config, oci_explorer, genai, mcp, adb, embeddings, reports, chat, terraform, settings, cis_engine) Also: README updated with OCIR auth instructions for manual docker run. 86 tests passing.
106 lines
4.7 KiB
Python
106 lines
4.7 KiB
Python
"""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(),
|
|
}
|