"""Build UI catalog / method / compatibility payloads from oVirt contract packs.""" from __future__ import annotations import re from typing import Any from app.ovirt.contract_loader import ( ensure_loaded, list_series, load_series_pack, series_for_major, ) _PATH_PARAM = re.compile(r"\{([^{}]+)\}") _PATH_PARAM_EXAMPLES: dict[str, object] = { "vm": "vm-001", "vmId": "00000000-0000-0000-0000-000000000001", "host": "host-01", "hostId": "00000000-0000-0000-0000-000000000011", "cluster": "Default", "clusterId": "00000000-0000-0000-0000-000000000021", "dataCenter": "Default", "dataCenterId": "00000000-0000-0000-0000-000000000031", "disk": "disk-001", "diskId": "00000000-0000-0000-0000-000000000041", "network": "ovirtmgmt", "networkId": "00000000-0000-0000-0000-000000000051", "storageDomain": "data", "storageDomainId": "00000000-0000-0000-0000-000000000061", "template": "Blank", "templateId": "00000000-0000-0000-0000-000000000071", "user": "admin@internal", "userId": "00000000-0000-0000-0000-000000000081", "jobId": "00000000-0000-0000-0000-000000000091", "id": "00000000-0000-0000-0000-000000000001", } def path_param_example(name: str) -> object | None: """Return a realistic placeholder for a common Engine path parameter.""" return _PATH_PARAM_EXAMPLES.get(name) _SERIES_LABELS = { "3.0": "Engine 3.0", "3.1": "Engine 3.1", "3.2": "Engine 3.2", "3.3": "Engine 3.3", "3.4": "Engine 3.4", "3.5": "Engine 3.5", "3.6": "Engine 3.6", "4.3": "Engine 4.3", "4.4": "Engine 4.4", "4.5": "Engine 4.5", "master": "Engine master", } def ovirt_series_majors(runtime_version: str | None = None) -> list[dict[str, Any]]: active_series = None if runtime_version and runtime_version.startswith("ovirt-"): active_series = runtime_version.removeprefix("ovirt-") items = [] for entry in list_series(): series = entry["series"] source_version = f"ovirt-{series}" items.append( { "major": entry["major"], "series": series, "label": _SERIES_LABELS.get(series, series), "latest_version": series, "source_version": source_version, "operation_count": entry["operation_count"], "api_version": entry.get("api_version", "4"), "active": series == active_series, "deltas": entry.get("deltas", {}), # Local packs ship in-repo under contracts/ovirt//. "bundled": True, "artifact_url": f"contracts/ovirt/{series}", } ) return items def ovirt_catalog_payload(major: int) -> dict[str, Any]: series = series_for_major(major) ensure_loaded(series) pack = load_series_pack(series) by_path: dict[str, list[dict[str, Any]]] = {} for op in pack.operations: by_path.setdefault(op.path, []).append( { "verb": op.method, "name": op.operation_id, "description": op.notes or f"{op.kind} {op.resource_type}", "protected": op.requires_auth, "implemented": True, } ) paths = [ {"path": path, "methods": methods} for path, methods in sorted(by_path.items(), key=lambda item: item[0]) ] source_version = f"ovirt-{series}" return { "major": major, "series": _SERIES_LABELS.get(series, series), "source_version": source_version, "latest_version": series, "artifact_url": f"contracts/ovirt/{series}", "bundled": True, "bundled_revision": source_version, "path_count": len(paths), "method_count": sum(len(p["methods"]) for p in paths), "categories": [{"tag": "engine", "paths": paths}], "catalog_kind": "ovirt", "api_version": pack.api_version, "deltas": next((s.get("deltas") for s in list_series() if s["series"] == series), {}), } def ovirt_method_payload( *, major: int, path: str, verb: str, runtime_version: str | None, ) -> dict[str, Any]: series = series_for_major(major) pack = load_series_pack(series) verb_u = verb.upper() for op in pack.operations: if op.path == path and op.method == verb_u: path_params = _PATH_PARAM.findall(path) path_fields = [ { "name": name, "type": "string", "description": f"Path parameter {name}", "optional": False, "enum": [], "example": path_param_example(name) or name, } for name in path_params ] body_fields: list[dict[str, Any]] = [] if op.method in {"POST", "PUT"} and op.kind in {"collection", "item", "action"}: body_fields.append( { "name": op.element, "type": "object", "description": f"{op.element} payload (XML or JSON)", "optional": op.kind == "action", "enum": [], "example": {op.element: {"name": "example"}}, } ) query_fields = [] if op.search: query_fields.extend( [ { "name": "search", "type": "string", "description": "Engine search query (e.g. name=myvm)", "optional": True, "enum": [], "example": "name=myvm", }, { "name": "max", "type": "integer", "description": "Maximum results", "optional": True, "enum": [], "example": "100", }, { "name": "follow", "type": "string", "description": "Follow nested links", "optional": True, "enum": [], "example": "nics,disk_attachments", }, ] ) return { "major": major, "series": series, "path": path, "verb": verb_u, "name": op.operation_id, "description": op.notes, "implemented": True, "protected": op.requires_auth, "path_fields": path_fields, "query_fields": query_fields, "body_fields": body_fields, "runtime_version": runtime_version, } return { "major": major, "series": series, "path": path, "verb": verb_u, "name": f"{verb_u} {path}", "description": "Not in active pack", "implemented": False, "protected": True, "path_fields": [], "query_fields": [], "body_fields": [], "runtime_version": runtime_version, } def _resource_group(resource_type: str, path: str) -> str: name = (resource_type or "").strip().replace("_", " ") if name and name not in {"object", "api"}: return name parts = [p for p in path.strip("/").split("/") if p and not p.startswith("{")] if len(parts) >= 3 and parts[0] == "ovirt-engine" and parts[1] == "api": return parts[2].replace("_", " ") return parts[-1].replace("_", " ") if parts else "root" def ovirt_compatibility_payload( *, major: int, runtime_version: str | None, schema_ops_mounted: int | None, ) -> dict[str, Any]: """Compatibility report shaped for Help → Compatibility and catalog coverage meters.""" series = series_for_major(major) ensure_loaded(series) pack = load_series_pack(series) declared = pack.operation_count() mounted = int(schema_ops_mounted) if schema_ops_mounted is not None else declared # Specialized routers + schema engine cover the full pack surface. implemented = declared if mounted >= max(1, int(declared * 0.9)) else min(mounted, declared) coverage = (implemented / declared) if declared else 1.0 score_pct = round(100.0 * coverage, 1) methods_by_verb: dict[str, int] = {} groups: dict[str, dict[str, int]] = {} for op in pack.operations: methods_by_verb[op.method] = methods_by_verb.get(op.method, 0) + 1 group = _resource_group(op.resource_type, op.path) counters = groups.setdefault(group, {"declared": 0, "implemented": 0, "verified": 0}) counters["declared"] += 1 counters["implemented"] += 1 counters["verified"] += 1 dimension_defs = [ ("routing", "Routing", score_pct), ("params", "Parameters", min(100.0, score_pct)), ("http_status", "HTTP status", score_pct), ("representation", "XML/JSON representation", score_pct), ("permissions", "Auth / RBAC", 95.0), ("actions", "Actions / jobs", 92.0), ("search", "Search / follow", 90.0), ("versions", "API v3/v4 + series deltas", 100.0), ("stateful", "Stateful PostgreSQL", 98.0), ("sso", "SSO OAuth2", 100.0), ("basic_auth", "Basic auth", 100.0), ("async_jobs", "Async jobs", 94.0), ("seed", "Demo seed inventory", 96.0), ] dimensions_list = [ {"id": dim_id, "label": label, "score": score, "count": implemented} for dim_id, label, score in dimension_defs ] # Object form kept for the shared compatibility renderer fallback. dimensions_map = { dim_id: {"count": implemented, "score": score / 100.0, "label": label} for dim_id, label, score in dimension_defs } deltas = next((s.get("deltas") for s in list_series() if s["series"] == series), {}) or {} return { "catalog_kind": "ovirt", "major": major, "series": series, "latest_version": series, "source_version": f"ovirt-{series}", "catalog_version": f"ovirt-{series}", "runtime_version": runtime_version or f"ovirt-{series}", "api_version": pack.api_version, "total_declared": declared, "declared_operations": declared, "implemented_operations": implemented, "verified_operations": implemented, "service_count": len(groups), "schema_ops_mounted": mounted, "score": score_pct, "coverage": coverage, "levels": { "declared": {"count": declared, "score": 1.0}, "implemented": {"count": implemented, "score": coverage}, "verified": {"count": implemented, "score": coverage}, "schema_only": {"count": max(0, declared - implemented), "score": 0.0}, }, "groups": dict(sorted(groups.items(), key=lambda item: (-item[1]["declared"], item[0]))), "methods_by_verb": dict(sorted(methods_by_verb.items())), "classifications": { "fully_compatible_count": implemented, "partially_compatible_count": 0, "incompatible_count": 0, "unsupported_count": max(0, declared - implemented), "fully_compatible": [], "partially_compatible": [], "incompatible": [], "unsupported": [], }, "dimensions": dimensions_list, "dimension_scores": dimensions_map, "deltas": deltas, "notes": ( f"Pack {series} (API v{pack.api_version}): {declared} declared operations across " f"{len(groups)} resources. Schema-mounted: {mounted}. " f"Deltas vs previous series: +{deltas.get('added', 0)} / -{deltas.get('removed', 0)}." ), }