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).
175 lines
5.4 KiB
Python
175 lines
5.4 KiB
Python
"""Resolve oVirt Engine series contract pack locations."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import threading
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from app.ovirt.opspec import OperationSpec, SeriesPack
|
|
|
|
_SERIES_MAJOR = {
|
|
"3.0": 30,
|
|
"3.1": 31,
|
|
"3.2": 32,
|
|
"3.3": 33,
|
|
"3.4": 34,
|
|
"3.5": 35,
|
|
"3.6": 36,
|
|
"4.3": 43,
|
|
"4.4": 44,
|
|
"4.5": 45,
|
|
"master": 50,
|
|
}
|
|
_MAJOR_SERIES = {v: k for k, v in _SERIES_MAJOR.items()}
|
|
|
|
|
|
def contracts_root() -> Path:
|
|
"""Locate ``contracts/ovirt`` for source, wheel, or Docker layouts."""
|
|
|
|
env = os.environ.get("OVIRT_CONTRACTS_ROOT")
|
|
if env:
|
|
return Path(env)
|
|
|
|
here = Path(__file__).resolve()
|
|
candidates = [
|
|
here.parents[2] / "contracts" / "ovirt", # repo checkout or site-packages
|
|
Path("/app/contracts/ovirt"), # runtime image WORKDIR layout
|
|
Path.cwd() / "contracts" / "ovirt",
|
|
]
|
|
for path in candidates:
|
|
if path.is_dir():
|
|
return path
|
|
return candidates[0]
|
|
|
|
|
|
def series_for_major(major: int) -> str:
|
|
return _MAJOR_SERIES.get(major, "4.5")
|
|
|
|
|
|
def major_for_series(series: str) -> int:
|
|
return _SERIES_MAJOR.get(series.lower(), 45)
|
|
|
|
|
|
def list_series() -> list[dict[str, Any]]:
|
|
root = contracts_root()
|
|
if not root.exists():
|
|
return []
|
|
result: list[dict[str, Any]] = []
|
|
for path in sorted(root.iterdir()):
|
|
man = path / "manifest.json"
|
|
if not man.is_file():
|
|
continue
|
|
data = json.loads(man.read_text())
|
|
result.append(
|
|
{
|
|
"series": data.get("series", path.name),
|
|
"major": data.get("major", major_for_series(path.name)),
|
|
"api_version": data.get("api_version", "4"),
|
|
"operation_count": data.get("operation_count", 0),
|
|
"service_count": data.get("service_count", 1),
|
|
"checksum": data.get("checksum", ""),
|
|
"generated_at": data.get("generated_at", ""),
|
|
"deltas": data.get("deltas", {}),
|
|
"product": data.get("product", {}),
|
|
}
|
|
)
|
|
return result
|
|
|
|
|
|
def _op_from_dict(raw: dict[str, Any]) -> OperationSpec:
|
|
return OperationSpec(
|
|
operation_id=str(raw["operation_id"]),
|
|
method=raw["method"],
|
|
path=str(raw["path"]),
|
|
resource_type=str(raw.get("resource_type") or "object"),
|
|
collection_key=str(raw.get("collection_key") or "objects"),
|
|
element=str(raw.get("element") or raw.get("resource_type") or "object"),
|
|
kind=str(raw.get("kind") or "collection"),
|
|
search=bool(raw.get("search", False)),
|
|
introduced_in=str(raw.get("introduced_in") or "3.0"),
|
|
requires_auth=bool(raw.get("requires_auth", True)),
|
|
status_code=int(raw.get("status_code") or 200),
|
|
create_status=int(raw.get("create_status") or 201),
|
|
notes=str(raw.get("notes") or ""),
|
|
response_fixture=raw.get("response_fixture"),
|
|
)
|
|
|
|
|
|
def load_series_pack(series: str) -> SeriesPack:
|
|
series = series.lower()
|
|
series_dir = contracts_root() / series
|
|
man_path = series_dir / "manifest.json"
|
|
api_path = series_dir / "api.json"
|
|
if not man_path.is_file() or not api_path.is_file():
|
|
raise FileNotFoundError(f"oVirt contract pack not found: {series_dir}")
|
|
man = json.loads(man_path.read_text())
|
|
data = json.loads(api_path.read_text())
|
|
ops = [_op_from_dict(raw) for raw in data.get("operations") or []]
|
|
return SeriesPack(
|
|
series=str(man.get("series", series)),
|
|
api_version=str(man.get("api_version") or data.get("api_version") or "4"),
|
|
major=int(man.get("major") or major_for_series(series)),
|
|
operations=ops,
|
|
product=dict(man.get("product") or data.get("product") or {}),
|
|
entry_point_links=list(man.get("entry_point_links") or []),
|
|
checksum=str(man.get("checksum") or ""),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class ContractRuntime:
|
|
series: str = "4.5"
|
|
pack: SeriesPack | None = None
|
|
_lock: threading.RLock = field(default_factory=threading.RLock)
|
|
|
|
def reload(self, series: str | None = None) -> dict[str, Any]:
|
|
with self._lock:
|
|
target = (series or self.series).lower()
|
|
self.pack = load_series_pack(target)
|
|
self.series = target
|
|
return self.summary()
|
|
|
|
def summary(self) -> dict[str, Any]:
|
|
with self._lock:
|
|
pack = self.pack
|
|
if pack is None:
|
|
return {
|
|
"series": self.series,
|
|
"operation_count": 0,
|
|
"major": major_for_series(self.series),
|
|
}
|
|
return {
|
|
"series": pack.series,
|
|
"major": pack.major,
|
|
"api_version": pack.api_version,
|
|
"operation_count": pack.operation_count(),
|
|
"service_count": 1,
|
|
"checksum": pack.checksum,
|
|
"product": pack.product,
|
|
"deltas": next(
|
|
(s.get("deltas") for s in list_series() if s["series"] == pack.series),
|
|
{},
|
|
),
|
|
}
|
|
|
|
|
|
_RUNTIME = ContractRuntime()
|
|
|
|
|
|
def get_runtime() -> ContractRuntime:
|
|
return _RUNTIME
|
|
|
|
|
|
def ensure_loaded(series: str = "4.5") -> ContractRuntime:
|
|
rt = get_runtime()
|
|
if rt.pack is None:
|
|
try:
|
|
rt.reload(series)
|
|
except FileNotFoundError:
|
|
pass
|
|
return rt
|