cbd0adca91
Stateful FastAPI lab with contract packs, Compose/Helm, Docker Hub release targets, and Pulumi coverage across all Engine series (GET/POST/PUT/DELETE/HEAD).
329 lines
12 KiB
Python
329 lines
12 KiB
Python
"""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/<series>/.
|
|
"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)}."
|
|
),
|
|
}
|