refactor: decompose monolith — app.py 10,600→143 lines, 30+ modules
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.
This commit is contained in:
105
backend/routes/cis_engine.py
Normal file
105
backend/routes/cis_engine.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""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(),
|
||||
}
|
||||
Reference in New Issue
Block a user