Initial commit: VMware vSphere API simulator scaffold.

Add the FastAPI app, PostgreSQL migrations, Docker/Helm packaging, API
contracts, docs, client examples, and the unit/integration/compatibility
test suite for local client and tooling labs without a real vCenter.
This commit is contained in:
2026-07-18 04:42:11 +03:00
commit f8d3cbdd59
422 changed files with 361335 additions and 0 deletions
+213
View File
@@ -0,0 +1,213 @@
"""HTML report for pulumi hybrid lab suite (pulumi-vsphere + REST + SOAP)."""
from __future__ import annotations
import html
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
def _esc(value: Any) -> str:
return html.escape(str(value), quote=True)
def _section_rest(summary: dict[str, Any]) -> str:
rest = summary.get("rest") or {}
majors = rest.get("majors") or []
by_verb = rest.get("by_verb") or {}
failures = rest.get("failures") or []
major_rows = []
for m in majors:
buckets = m.get("buckets") or {}
deep = m.get("deep_vs_stub") or {}
major_rows.append(
"<tr>"
f"<td>{_esc(m.get('major'))}</td>"
f"<td>{_esc(m.get('version'))}</td>"
f"<td>{_esc(m.get('probed'))}</td>"
f"<td>{_esc(m.get('failed'))}</td>"
f"<td>{_esc(buckets.get('success_2xx', 0))}</td>"
f"<td>{_esc(buckets.get('client_4xx', 0))}</td>"
f"<td>{_esc(buckets.get('server_5xx', 0))}</td>"
f"<td>{_esc(deep.get('deep', 0))}/{_esc(deep.get('stub', 0))}</td>"
"</tr>"
)
verb_bits = " · ".join(f"{_esc(k)}={_esc(v)}" for k, v in sorted(by_verb.items()))
fail_rows = []
for f in failures[:40]:
fail_rows.append(
"<tr>"
f"<td>{_esc(f.get('major'))}</td>"
f"<td>{_esc(f.get('verb'))}</td>"
f"<td><code>{_esc(f.get('path'))}</code></td>"
f"<td>{_esc(f.get('kind'))}</td>"
f"<td>{_esc(f.get('status'))}</td>"
f"<td class='err'>{_esc((f.get('body') or f.get('expected') or '')[:180])}</td>"
"</tr>"
)
return f"""
<h2>REST matrix</h2>
<p class="meta">total={_esc(rest.get("total"))} · failed={_esc(rest.get("failed"))}
· verbs: {verb_bits or ""}</p>
<table>
<thead><tr><th>Major</th><th>Version</th><th>Probed</th><th>Failed</th>
<th>2xx</th><th>4xx</th><th>5xx</th><th>deep/stub</th></tr></thead>
<tbody>{"".join(major_rows) or '<tr><td colspan="8">—</td></tr>'}</tbody>
</table>
<h3>REST failures (sample)</h3>
<table>
<thead><tr><th>Major</th><th>Verb</th><th>Path</th><th>Kind</th><th>Status</th><th>Detail</th></tr></thead>
<tbody>{"".join(fail_rows) or '<tr><td colspan="6">none</td></tr>'}</tbody>
</table>
"""
def _section_crud(summary: dict[str, Any]) -> str:
crud = summary.get("crud") or {}
flows = crud.get("flows") or []
rows = []
for f in flows:
rows.append(
f"<tr class='{_esc(f.get('status'))}'>"
f"<td>{_esc(f.get('flow'))}</td>"
f"<td>{_esc(f.get('step'))}</td>"
f"<td>{_esc(f.get('status'))}</td>"
f"<td class='err'>{_esc(f.get('error') or '')}</td>"
f"</tr>"
)
return f"""
<h2>Deep REST CRUD</h2>
<p class="meta">total={_esc(crud.get("total"))} · failed={_esc(crud.get("failed"))}</p>
<table>
<thead><tr><th>Flow</th><th>Step</th><th>Status</th><th>Detail</th></tr></thead>
<tbody>{"".join(rows) or '<tr><td colspan="4">— (smoke skips CRUD)</td></tr>'}</tbody>
</table>
"""
def _section_soap(summary: dict[str, Any]) -> str:
soap = summary.get("soap") or {}
ops = soap.get("ops") or []
rows = []
for op in ops:
status = "passed" if op.get("ok") else "failed"
rows.append(
f"<tr class='{_esc(status)}'>"
f"<td><code>{_esc(op.get('op'))}</code></td>"
f"<td>{_esc(op.get('status'))}</td>"
f"<td>{_esc(status)}</td>"
f"<td class='err'>{_esc(op.get('error') or '')}</td>"
f"</tr>"
)
return f"""
<h2>SOAP WSDL ops</h2>
<p class="meta">total={_esc(soap.get("total"))} · failed={_esc(soap.get("failed"))}</p>
<table>
<thead><tr><th>Operation</th><th>HTTP</th><th>Status</th><th>Detail</th></tr></thead>
<tbody>{"".join(rows) or '<tr><td colspan="4">— (smoke skips SOAP)</td></tr>'}</tbody>
</table>
"""
def render_html(summary: dict[str, Any]) -> str:
generated = summary.get("generated_at") or datetime.now(UTC).isoformat()
cases = summary.get("cases") or []
total = len(cases)
passed = sum(1 for c in cases if c.get("status") == "passed")
failed = sum(1 for c in cases if c.get("status") == "failed")
skipped = sum(1 for c in cases if c.get("status") == "skipped")
rest = summary.get("rest") or {}
crud = summary.get("crud") or {}
soap = summary.get("soap") or {}
rows = []
for case in cases:
status = case.get("status") or ""
outs = case.get("outputs") or {}
out_html = "<br/>".join(f"<code>{_esc(k)}</code>={_esc(v)}" for k, v in outs.items())
err = _esc(case.get("error") or "")
rows.append(
f"<tr class='{_esc(status)}'>"
f"<td>{_esc(case.get('id'))}</td>"
f"<td>{_esc(case.get('title'))}</td>"
f"<td>{_esc(status)}</td>"
f"<td class='outs'>{out_html or ''}</td>"
f"<td class='err'>{err or ''}</td>"
f"</tr>"
)
return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>pulumi hybrid lab report</title>
<style>
:root {{
--bg:#0f1419; --panel:#1a222c; --text:#e7eef7; --muted:#9aa8b8;
--ok:#3dd68c; --fail:#ff6b6b; --skip:#f0c14a; --line:#2a3542; --accent:#5b9fd4;
}}
body {{ margin:0; font-family:"IBM Plex Sans","Segoe UI",sans-serif; background:var(--bg); color:var(--text); }}
header, main {{ max-width:1200px; margin:0 auto; padding:1.5rem; }}
h1 {{ margin:0 0 .4rem; }}
h2 {{ margin:1.75rem 0 .6rem; color:var(--accent); }}
h3 {{ margin:1rem 0 .4rem; font-size:1rem; color:var(--muted); }}
.meta {{ color:var(--muted); }}
.summary {{ display:grid; grid-template-columns:repeat(4,1fr); gap:.75rem; margin:1rem 0 1.5rem; }}
.stat {{ background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:1rem; }}
.stat strong {{ display:block; font-size:1.5rem; margin-top:.25rem; }}
.ok strong {{ color:var(--ok); }} .fail strong {{ color:var(--fail); }} .skip strong {{ color:var(--skip); }}
table {{ width:100%; border-collapse:collapse; background:var(--panel); border:1px solid var(--line);
border-radius:12px; overflow:hidden; margin-bottom:1rem; }}
th, td {{ padding:.55rem .65rem; border-bottom:1px solid var(--line); text-align:left; vertical-align:top; font-size:.9rem; }}
th {{ color:var(--accent); }}
tr.failed td:nth-child(3) {{ color:var(--fail); font-weight:600; }}
tr.passed td:nth-child(3) {{ color:var(--ok); }}
tr.skipped td:nth-child(3) {{ color:var(--skip); }}
code {{ font-family:ui-monospace,Menlo,monospace; font-size:.8rem; }}
.outs, .err {{ word-break:break-word; }}
.metrics {{ display:grid; grid-template-columns:repeat(3,1fr); gap:.75rem; margin-bottom:1rem; }}
</style>
</head>
<body>
<header>
<h1>pulumi hybrid lab suite</h1>
<div class="meta">Generated {_esc(generated)} · server {_esc(summary.get("vsphere_server"))}
· base {_esc(summary.get("vsphere_base"))}
· smoke={_esc(summary.get("smoke"))}</div>
</header>
<main>
<section class="summary">
<div class="stat"><span>Cases</span><strong>{total}</strong></div>
<div class="stat ok"><span>Passed</span><strong>{passed}</strong></div>
<div class="stat fail"><span>Failed</span><strong>{failed}</strong></div>
<div class="stat skip"><span>Skipped</span><strong>{skipped}</strong></div>
</section>
<section class="metrics">
<div class="stat"><span>REST failed</span><strong>{_esc(rest.get("failed", 0))}</strong>
<span class="meta"> / {_esc(rest.get("total", 0))} probes</span></div>
<div class="stat"><span>CRUD failed</span><strong>{_esc(crud.get("failed", 0))}</strong>
<span class="meta"> / {_esc(crud.get("total", 0))} flows</span></div>
<div class="stat"><span>SOAP failed</span><strong>{_esc(soap.get("failed", 0))}</strong>
<span class="meta"> / {_esc(soap.get("total", 0))} ops</span></div>
</section>
<h2>Suite cases</h2>
<table>
<thead><tr><th>ID</th><th>Title</th><th>Status</th><th>Outputs</th><th>Error</th></tr></thead>
<tbody>{"".join(rows)}</tbody>
</table>
{_section_rest(summary)}
{_section_crud(summary)}
{_section_soap(summary)}
</main>
</body>
</html>
"""
def write_report(summary: dict[str, Any], path: Path) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(render_html(summary), encoding="utf-8")