intermediate commit
This commit is contained in:
146
tools/release/govoplan_release/module_directory.py
Normal file
146
tools/release/govoplan_release/module_directory.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""Generate browsable module-directory artifacts from a release catalog."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from .catalog import DEFAULT_PUBLIC_BASE_URL, canonical_hash
|
||||
from .release_intelligence import compatibility_rows, module_rows, signature_rows
|
||||
|
||||
|
||||
def write_module_directory(
|
||||
*,
|
||||
catalog_payload: dict[str, Any],
|
||||
keyring_payload: dict[str, Any],
|
||||
output_root: Path,
|
||||
channel: str,
|
||||
public_base_url: str = DEFAULT_PUBLIC_BASE_URL,
|
||||
) -> tuple[Path, ...]:
|
||||
payloads = module_directory_payloads(
|
||||
catalog_payload=catalog_payload,
|
||||
keyring_payload=keyring_payload,
|
||||
channel=channel,
|
||||
public_base_url=public_base_url,
|
||||
)
|
||||
written: list[Path] = []
|
||||
for relative_path, payload in payloads:
|
||||
path = output_root / relative_path
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
written.append(path)
|
||||
return tuple(written)
|
||||
|
||||
|
||||
def module_directory_payloads(
|
||||
*,
|
||||
catalog_payload: dict[str, Any],
|
||||
keyring_payload: dict[str, Any],
|
||||
channel: str,
|
||||
public_base_url: str = DEFAULT_PUBLIC_BASE_URL,
|
||||
) -> tuple[tuple[Path, dict[str, Any]], ...]:
|
||||
generated_at = json_datetime(datetime.now(tz=UTC))
|
||||
modules = module_rows(catalog_payload)
|
||||
compatibility = compatibility_rows(modules)
|
||||
signatures = signature_rows(catalog_payload, keyring_payload)
|
||||
base_url = public_base_url.rstrip("/")
|
||||
catalog_url = f"{base_url}/catalogs/v1/channels/{channel}.json"
|
||||
keyring_url = f"{base_url}/catalogs/v1/keyring.json"
|
||||
catalog_hash = canonical_hash(catalog_payload)
|
||||
catalog_sequence = catalog_payload.get("sequence")
|
||||
|
||||
files: list[tuple[Path, dict[str, Any]]] = []
|
||||
module_index_entries: list[dict[str, Any]] = []
|
||||
for module in modules:
|
||||
module_id = str(module.get("module_id") or module.get("repo") or "")
|
||||
if not module_id:
|
||||
continue
|
||||
version = str(module.get("version") or "0.0.0")
|
||||
module_slug = safe_path_part(module_id)
|
||||
version_slug = safe_path_part(version)
|
||||
module_base = f"{base_url}/catalogs/v1/modules/{module_slug}"
|
||||
manifest_url = f"{module_base}/{version_slug}/manifest.json"
|
||||
module_index_url = f"{module_base}/index.json"
|
||||
module_compatibility = [row for row in compatibility if row.get("module_id") == module_id]
|
||||
manifest = {
|
||||
"manifest_version": "1",
|
||||
"generated_at": generated_at,
|
||||
"channel": channel,
|
||||
"module": module,
|
||||
"compatibility": module_compatibility,
|
||||
"release": {
|
||||
"catalog_url": catalog_url,
|
||||
"keyring_url": keyring_url,
|
||||
"catalog_hash": catalog_hash,
|
||||
"catalog_sequence": catalog_sequence,
|
||||
"signatures": signatures,
|
||||
},
|
||||
}
|
||||
files.append((Path("modules") / module_slug / version_slug / "manifest.json", manifest))
|
||||
files.append(
|
||||
(
|
||||
Path("modules") / module_slug / "index.json",
|
||||
{
|
||||
"index_version": "1",
|
||||
"generated_at": generated_at,
|
||||
"channel": channel,
|
||||
"module_id": module_id,
|
||||
"name": module.get("name"),
|
||||
"latest_version": version,
|
||||
"latest_manifest_url": manifest_url,
|
||||
"versions": [
|
||||
{
|
||||
"version": version,
|
||||
"manifest_url": manifest_url,
|
||||
"python_tag": module.get("python_tag"),
|
||||
"webui_tag": module.get("webui_tag"),
|
||||
}
|
||||
],
|
||||
"release": {
|
||||
"catalog_url": catalog_url,
|
||||
"keyring_url": keyring_url,
|
||||
"catalog_hash": catalog_hash,
|
||||
"catalog_sequence": catalog_sequence,
|
||||
},
|
||||
},
|
||||
)
|
||||
)
|
||||
module_index_entries.append(
|
||||
{
|
||||
"module_id": module_id,
|
||||
"name": module.get("name"),
|
||||
"latest_version": version,
|
||||
"index_url": module_index_url,
|
||||
"latest_manifest_url": manifest_url,
|
||||
"repo": module.get("repo"),
|
||||
}
|
||||
)
|
||||
|
||||
files.append(
|
||||
(
|
||||
Path("modules") / "index.json",
|
||||
{
|
||||
"index_version": "1",
|
||||
"generated_at": generated_at,
|
||||
"channel": channel,
|
||||
"catalog_url": catalog_url,
|
||||
"keyring_url": keyring_url,
|
||||
"catalog_hash": catalog_hash,
|
||||
"catalog_sequence": catalog_sequence,
|
||||
"module_count": len(module_index_entries),
|
||||
"modules": sorted(module_index_entries, key=lambda item: str(item.get("module_id"))),
|
||||
},
|
||||
)
|
||||
)
|
||||
return tuple(files)
|
||||
|
||||
|
||||
def safe_path_part(value: str) -> str:
|
||||
return re.sub(r"[^A-Za-z0-9_.-]+", "_", value.strip()) or "_"
|
||||
|
||||
|
||||
def json_datetime(value: datetime) -> str:
|
||||
return value.replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
||||
Reference in New Issue
Block a user