Mark installed catalog modules as updates
This commit is contained in:
@@ -438,7 +438,7 @@ def _upsert_install_plan_item(session: Session, item: ModuleInstallPlanItem):
|
|||||||
return save_module_install_plan(session, [*retained, item])
|
return save_module_install_plan(session, [*retained, item])
|
||||||
|
|
||||||
|
|
||||||
def _catalog_plan_item(module_id: str) -> tuple[ModuleInstallPlanItem, dict[str, object]]:
|
def _catalog_plan_item(module_id: str, available_module_ids: set[str]) -> tuple[ModuleInstallPlanItem, dict[str, object]]:
|
||||||
result = validate_module_package_catalog()
|
result = validate_module_package_catalog()
|
||||||
if not result.get("valid"):
|
if not result.get("valid"):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -448,15 +448,17 @@ def _catalog_plan_item(module_id: str) -> tuple[ModuleInstallPlanItem, dict[str,
|
|||||||
for raw_item in result.get("modules", []):
|
for raw_item in result.get("modules", []):
|
||||||
if not isinstance(raw_item, dict):
|
if not isinstance(raw_item, dict):
|
||||||
continue
|
continue
|
||||||
if raw_item.get("module_id") != module_id or raw_item.get("action") != "install":
|
if raw_item.get("module_id") != module_id or raw_item.get("action") not in {"install", "update"}:
|
||||||
continue
|
continue
|
||||||
|
raw_action = raw_item.get("action")
|
||||||
|
action = "update" if raw_action == "update" or module_id in available_module_ids else "install"
|
||||||
license_decision = module_license_decision(_catalog_license_features(raw_item))
|
license_decision = module_license_decision(_catalog_license_features(raw_item))
|
||||||
if not license_decision.get("allowed"):
|
if not license_decision.get("allowed"):
|
||||||
missing = license_decision.get("missing_features")
|
missing = license_decision.get("missing_features")
|
||||||
missing_text = ", ".join(str(item) for item in missing) if isinstance(missing, list) else "required feature"
|
missing_text = ", ".join(str(item) for item in missing) if isinstance(missing, list) else "required feature"
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail=f"License does not allow installing {module_id}: {missing_text}.",
|
detail=f"License does not allow {action} for {module_id}: {missing_text}.",
|
||||||
)
|
)
|
||||||
notes = raw_item.get("notes") if isinstance(raw_item.get("notes"), str) else None
|
notes = raw_item.get("notes") if isinstance(raw_item.get("notes"), str) else None
|
||||||
if license_decision.get("reason") and license_decision.get("missing_features"):
|
if license_decision.get("reason") and license_decision.get("missing_features"):
|
||||||
@@ -464,7 +466,7 @@ def _catalog_plan_item(module_id: str) -> tuple[ModuleInstallPlanItem, dict[str,
|
|||||||
notes = f"{prefix}License warning: {license_decision['reason']}"
|
notes = f"{prefix}License warning: {license_decision['reason']}"
|
||||||
return ModuleInstallPlanItem(
|
return ModuleInstallPlanItem(
|
||||||
module_id=str(raw_item["module_id"]),
|
module_id=str(raw_item["module_id"]),
|
||||||
action="install",
|
action=action,
|
||||||
source="catalog",
|
source="catalog",
|
||||||
catalog=_catalog_plan_metadata(result),
|
catalog=_catalog_plan_metadata(result),
|
||||||
python_package=raw_item.get("python_package") if isinstance(raw_item.get("python_package"), str) else None,
|
python_package=raw_item.get("python_package") if isinstance(raw_item.get("python_package"), str) else None,
|
||||||
@@ -473,7 +475,7 @@ def _catalog_plan_item(module_id: str) -> tuple[ModuleInstallPlanItem, dict[str,
|
|||||||
webui_ref=raw_item.get("webui_ref") if isinstance(raw_item.get("webui_ref"), str) else None,
|
webui_ref=raw_item.get("webui_ref") if isinstance(raw_item.get("webui_ref"), str) else None,
|
||||||
notes=notes,
|
notes=notes,
|
||||||
), result
|
), result
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Catalog install entry not found: {module_id}")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Catalog install/update entry not found: {module_id}")
|
||||||
|
|
||||||
|
|
||||||
def _catalog_plan_metadata(validation: dict[str, object]) -> dict[str, object]:
|
def _catalog_plan_metadata(validation: dict[str, object]) -> dict[str, object]:
|
||||||
@@ -796,7 +798,9 @@ def plan_module_install_from_catalog(
|
|||||||
principal: ApiPrincipal = Depends(require_scope("system:settings:write")),
|
principal: ApiPrincipal = Depends(require_scope("system:settings:write")),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
item, validation = _catalog_plan_item(module_id)
|
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)
|
||||||
|
item, validation = _catalog_plan_item(module_id, set(available))
|
||||||
plan = _upsert_install_plan_item(session, item)
|
plan = _upsert_install_plan_item(session, item)
|
||||||
record_module_package_catalog_acceptance(validation)
|
record_module_package_catalog_acceptance(validation)
|
||||||
except ModuleManagementError as exc:
|
except ModuleManagementError as exc:
|
||||||
@@ -820,7 +824,8 @@ def plan_module_install_from_catalog(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
session.commit()
|
session.commit()
|
||||||
return _module_install_plan_response(session, request, notes=[f"Catalog install entry planned for {module_id}."])
|
planned_action = "update" if item.action == "update" else "install"
|
||||||
|
return _module_install_plan_response(session, request, notes=[f"Catalog {planned_action} entry planned for {module_id}."])
|
||||||
|
|
||||||
|
|
||||||
@router.post("/system/modules/{module_id}/uninstall-plan", response_model=ModuleInstallPlanResponse)
|
@router.post("/system/modules/{module_id}/uninstall-plan", response_model=ModuleInstallPlanResponse)
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ class ModuleInstallPlanItem(BaseModel):
|
|||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
module_id: str = Field(min_length=1, max_length=120)
|
module_id: str = Field(min_length=1, max_length=120)
|
||||||
action: Literal["install", "uninstall"]
|
action: Literal["install", "update", "uninstall"]
|
||||||
source: Literal["manual", "catalog"] = "manual"
|
source: Literal["manual", "catalog"] = "manual"
|
||||||
catalog: dict[str, Any] | None = None
|
catalog: dict[str, Any] | None = None
|
||||||
python_package: str | None = Field(default=None, max_length=200)
|
python_package: str | None = Field(default=None, max_length=200)
|
||||||
@@ -327,7 +327,7 @@ class ModulePackageCatalogItem(BaseModel):
|
|||||||
name: str
|
name: str
|
||||||
description: str | None = None
|
description: str | None = None
|
||||||
version: str | None = None
|
version: str | None = None
|
||||||
action: Literal["install", "uninstall"] = "install"
|
action: Literal["install", "update", "uninstall"] = "install"
|
||||||
python_package: str | None = None
|
python_package: str | None = None
|
||||||
python_ref: str | None = None
|
python_ref: str | None = None
|
||||||
webui_package: str | None = None
|
webui_package: str | None = None
|
||||||
|
|||||||
Reference in New Issue
Block a user