Expose public module catalog discovery
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -74,7 +75,11 @@ from govoplan_core.core.module_installer_notifications import (
|
||||
installer_notification_subject,
|
||||
)
|
||||
from govoplan_core.core.module_license import module_license_decision, module_license_diagnostics
|
||||
from govoplan_core.core.module_package_catalog import record_module_package_catalog_acceptance, validate_module_package_catalog
|
||||
from govoplan_core.core.module_package_catalog import (
|
||||
record_module_package_catalog_acceptance,
|
||||
validate_module_package_catalog,
|
||||
validate_official_module_package_catalog,
|
||||
)
|
||||
from govoplan_core.core.maintenance import MAINTENANCE_ACCESS_SCOPE, MaintenanceMode, saved_maintenance_mode, save_maintenance_mode
|
||||
from govoplan_core.core.lifecycle import ModuleLifecycleManager
|
||||
from govoplan_core.core.registry import PlatformRegistry
|
||||
@@ -589,7 +594,7 @@ def _catalog_plan_item(
|
||||
*,
|
||||
validation: dict[str, object] | None = None,
|
||||
) -> tuple[ModuleInstallPlanItem, dict[str, object]]:
|
||||
result = validation or validate_module_package_catalog()
|
||||
result = validation or _module_package_catalog_validation()
|
||||
_require_valid_module_catalog(result)
|
||||
raw_item = _catalog_install_or_update_item(result, module_id)
|
||||
action = _catalog_plan_action(raw_item, module_id, available_module_ids)
|
||||
@@ -604,6 +609,7 @@ def _catalog_plan_item(
|
||||
python_ref=_catalog_string(raw_item, "python_ref"),
|
||||
webui_package=_catalog_string(raw_item, "webui_package"),
|
||||
webui_ref=_catalog_string(raw_item, "webui_ref"),
|
||||
artifact_integrity=_catalog_mapping(raw_item, "artifact_integrity"),
|
||||
notes=_catalog_plan_notes(raw_item, license_decision),
|
||||
), result
|
||||
|
||||
@@ -616,6 +622,13 @@ def _require_valid_module_catalog(result: dict[str, object]) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _module_package_catalog_validation() -> dict[str, object]:
|
||||
configured = validate_module_package_catalog()
|
||||
if configured.get("configured") or configured.get("error"):
|
||||
return configured
|
||||
return validate_official_module_package_catalog()
|
||||
|
||||
|
||||
def _catalog_install_or_update_item(result: dict[str, object], module_id: str) -> dict[str, object]:
|
||||
for raw_item in result.get("modules", []):
|
||||
if not isinstance(raw_item, dict):
|
||||
@@ -659,6 +672,11 @@ def _catalog_string(raw_item: dict[str, object], field: str) -> str | None:
|
||||
return value if isinstance(value, str) else None
|
||||
|
||||
|
||||
def _catalog_mapping(raw_item: dict[str, object], field: str) -> dict[str, object] | None:
|
||||
value = raw_item.get(field)
|
||||
return dict(value) if isinstance(value, Mapping) else None
|
||||
|
||||
|
||||
def _catalog_plan_metadata(validation: dict[str, object]) -> dict[str, object]:
|
||||
metadata: dict[str, object] = {
|
||||
"source": validation.get("source") or validation.get("path"),
|
||||
@@ -688,8 +706,21 @@ def _catalog_license_features(raw_item: dict[str, object]) -> list[str]:
|
||||
return [str(item).strip() for item in value if str(item).strip()]
|
||||
|
||||
|
||||
def _module_package_catalog_item(raw_item: dict[str, object]) -> ModulePackageCatalogItem:
|
||||
def _module_package_catalog_item(
|
||||
raw_item: dict[str, object],
|
||||
available: Mapping[str, Any],
|
||||
) -> ModulePackageCatalogItem:
|
||||
payload = dict(raw_item)
|
||||
manifest = available.get(str(raw_item.get("module_id") or ""))
|
||||
installed_version = str(getattr(manifest, "version", "") or "") or None
|
||||
target_version = raw_item.get("version")
|
||||
payload["installed"] = manifest is not None
|
||||
payload["installed_version"] = installed_version
|
||||
payload["update_available"] = bool(
|
||||
installed_version
|
||||
and isinstance(target_version, str)
|
||||
and target_version != installed_version
|
||||
)
|
||||
decision = module_license_decision(_catalog_license_features(raw_item))
|
||||
payload["license_allowed"] = bool(decision.get("allowed"))
|
||||
payload["license_enforced"] = bool(decision.get("enforced"))
|
||||
@@ -1218,13 +1249,20 @@ def retry_module_install_request(
|
||||
|
||||
@router.get("/system/modules/package-catalog", response_model=ModulePackageCatalogResponse)
|
||||
def read_module_package_catalog(
|
||||
request: Request,
|
||||
principal: ApiPrincipal = Depends(require_scope("system:settings:read")),
|
||||
):
|
||||
del principal
|
||||
result = validate_module_package_catalog()
|
||||
result = _module_package_catalog_validation()
|
||||
lifecycle = getattr(request.app.state, "govoplan_lifecycle", None)
|
||||
available = (
|
||||
dict(lifecycle.available_modules)
|
||||
if isinstance(lifecycle, ModuleLifecycleManager)
|
||||
else available_module_manifests(ignore_load_errors=True)
|
||||
)
|
||||
license_diagnostics = module_license_diagnostics(required_features=_catalog_required_license_features(result))
|
||||
return ModulePackageCatalogResponse(
|
||||
modules=[_module_package_catalog_item(item) for item in result["modules"] if isinstance(item, dict)],
|
||||
modules=[_module_package_catalog_item(item, available) for item in result["modules"] if isinstance(item, dict)],
|
||||
configured=bool(result["configured"]),
|
||||
valid=bool(result["valid"]),
|
||||
path=result["path"] if isinstance(result["path"], str) else None,
|
||||
|
||||
@@ -393,6 +393,9 @@ class ModulePackageCatalogItem(BaseModel):
|
||||
description: str | None = None
|
||||
version: str | None = None
|
||||
action: Literal["install", "update", "uninstall"] = "install"
|
||||
installed: bool = False
|
||||
installed_version: str | None = None
|
||||
update_available: bool = False
|
||||
dependencies: list[str] = Field(default_factory=list)
|
||||
optional_dependencies: list[str] = Field(default_factory=list)
|
||||
migration_safety: Literal["automatic", "requires_review", "forward_only", "destructive"] = "automatic"
|
||||
|
||||
Reference in New Issue
Block a user