update check active
This commit is contained in:
@@ -18,6 +18,18 @@ from app.models.schemas import InstallRequest, RemoveRequest, UpdateRequest
|
||||
from app.storage.repository import Repository, utc_now
|
||||
|
||||
|
||||
APT_SERVICE_POLICIES: dict[str, dict[str, Any]] = {
|
||||
"postgresql": {
|
||||
"managedServices": ["postgresql.service"],
|
||||
"readiness": "postgresql",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class InstalledComponentVerificationError(RuntimeError):
|
||||
"""The package changed on disk, but its required runtime verification failed."""
|
||||
|
||||
|
||||
class TaskRunner:
|
||||
def __init__(self, repository: Repository) -> None:
|
||||
self.repository = repository
|
||||
@@ -25,6 +37,7 @@ class TaskRunner:
|
||||
self.manifest_validator = ManifestValidator()
|
||||
|
||||
def run_install(self, task_id: str, request: InstallRequest | UpdateRequest, task_type: str = "install") -> None:
|
||||
manifest: dict[str, Any] | None = None
|
||||
try:
|
||||
self._mark_started(task_id, f"starting {task_type}")
|
||||
self._require_root_if_available()
|
||||
@@ -49,6 +62,9 @@ class TaskRunner:
|
||||
finished_at=utc_now(),
|
||||
)
|
||||
self.repository.add_log(task_id, "info", f"Task {task_id} completed")
|
||||
except InstalledComponentVerificationError as error:
|
||||
self._track_attention_install(task_id, manifest)
|
||||
self._fail_task(task_id, error)
|
||||
except Exception as error:
|
||||
self._fail_task(task_id, error)
|
||||
|
||||
@@ -332,30 +348,121 @@ class TaskRunner:
|
||||
installed_component = dict(component)
|
||||
installed_component["version"] = installed_version
|
||||
|
||||
if package_name == "postgresql":
|
||||
service_name = "postgresql.service"
|
||||
policy = APT_SERVICE_POLICIES.get(package_name, {})
|
||||
managed_services = list(policy.get("managedServices", []))
|
||||
discovered_services = installer.discover_service_units(package_name)
|
||||
service_names = list(dict.fromkeys([*managed_services, *discovered_services]))
|
||||
|
||||
self.repository.update_task_component(
|
||||
task_id,
|
||||
component_id,
|
||||
progress=80,
|
||||
current_step="checking package services",
|
||||
service_check_status="checking",
|
||||
service_checks=[],
|
||||
)
|
||||
|
||||
verification_errors: list[str] = []
|
||||
if managed_services:
|
||||
self.repository.update_task_component(
|
||||
task_id,
|
||||
component_id,
|
||||
progress=85,
|
||||
current_step="starting PostgreSQL service",
|
||||
current_step="starting managed package services",
|
||||
)
|
||||
services.enable_service(service_name)
|
||||
services.start_service(service_name)
|
||||
services.assert_service_active(service_name)
|
||||
for service_name in managed_services:
|
||||
try:
|
||||
services.enable_service(service_name)
|
||||
services.start_service(service_name)
|
||||
except Exception as error:
|
||||
verification_errors.append(f"Could not start {service_name}: {error}")
|
||||
|
||||
readiness_status: str | None = None
|
||||
if policy.get("readiness") == "postgresql":
|
||||
self.repository.update_task_component(
|
||||
task_id,
|
||||
component_id,
|
||||
progress=95,
|
||||
progress=90,
|
||||
current_step="checking PostgreSQL readiness",
|
||||
)
|
||||
installer.wait_for_postgresql()
|
||||
self.repository.add_log(task_id, "info", "PostgreSQL service is active and accepting connections")
|
||||
installed_component["serviceName"] = service_name
|
||||
try:
|
||||
installer.wait_for_postgresql()
|
||||
readiness_status = "ready"
|
||||
except Exception as error:
|
||||
readiness_status = "failed"
|
||||
verification_errors.append(f"PostgreSQL is not accepting connections: {error}")
|
||||
|
||||
service_checks: list[dict[str, Any]] = []
|
||||
self.repository.update_task_component(
|
||||
task_id,
|
||||
component_id,
|
||||
progress=95,
|
||||
current_step="reading service status",
|
||||
)
|
||||
for service_name in service_names:
|
||||
check = services.get_service_status(service_name)
|
||||
if service_name in managed_services and readiness_status is not None:
|
||||
check["readinessType"] = str(policy["readiness"])
|
||||
check["readinessStatus"] = readiness_status
|
||||
check["healthy"] = bool(check.get("healthy")) and readiness_status == "ready"
|
||||
service_checks.append(check)
|
||||
|
||||
if check.get("healthy"):
|
||||
self.repository.add_log(
|
||||
task_id,
|
||||
"info",
|
||||
f"Service {service_name} is active ({check.get('subState', 'unknown')})",
|
||||
)
|
||||
else:
|
||||
self.repository.add_log(
|
||||
task_id,
|
||||
"warning",
|
||||
f"Service {service_name} is not healthy "
|
||||
f"(load={check.get('loadState', 'unknown')}, "
|
||||
f"active={check.get('activeState', 'unknown')}, "
|
||||
f"sub={check.get('subState', 'unknown')})",
|
||||
)
|
||||
|
||||
if not service_names:
|
||||
service_check_status = "not-applicable"
|
||||
self.repository.add_log(
|
||||
task_id,
|
||||
"info",
|
||||
f"APT package {package_name} does not expose a concrete systemd service unit",
|
||||
)
|
||||
elif all(bool(check.get("healthy")) for check in service_checks):
|
||||
service_check_status = "healthy"
|
||||
else:
|
||||
service_check_status = "unhealthy"
|
||||
|
||||
self.repository.update_task_component(
|
||||
task_id,
|
||||
component_id,
|
||||
progress=98,
|
||||
current_step="service checks completed",
|
||||
service_check_status=service_check_status,
|
||||
service_checks=service_checks,
|
||||
)
|
||||
installed_component["serviceCheckStatus"] = service_check_status
|
||||
installed_component["serviceChecks"] = service_checks
|
||||
if managed_services:
|
||||
installed_component["serviceName"] = managed_services[0]
|
||||
self.repository.upsert_installed_component(app_id, installed_component)
|
||||
|
||||
unhealthy_managed_services = [
|
||||
str(check.get("serviceName"))
|
||||
for check in service_checks
|
||||
if check.get("serviceName") in managed_services and not check.get("healthy")
|
||||
]
|
||||
if unhealthy_managed_services:
|
||||
verification_errors.append(
|
||||
f"Required service is not healthy: {', '.join(unhealthy_managed_services)}"
|
||||
)
|
||||
if verification_errors:
|
||||
raise InstalledComponentVerificationError(
|
||||
"; ".join(dict.fromkeys(verification_errors))
|
||||
)
|
||||
|
||||
def _install_docker_component(self, task_id: str, app_id: str, component: dict[str, Any]) -> None:
|
||||
component_id = component["componentId"]
|
||||
container_name = component["containerName"]
|
||||
@@ -426,6 +533,33 @@ class TaskRunner:
|
||||
except Exception as error:
|
||||
self.repository.add_log(task_id, "warning", f"Could not {action}: {error}")
|
||||
|
||||
def _track_attention_install(self, task_id: str, manifest: dict[str, Any] | None) -> None:
|
||||
if not manifest:
|
||||
return
|
||||
try:
|
||||
manifest_hash = hashlib.sha256(
|
||||
self.repository.export_manifest_hash(manifest).encode("utf-8")
|
||||
).hexdigest()
|
||||
self.repository.upsert_installed_app(
|
||||
manifest["appId"],
|
||||
manifest["appName"],
|
||||
manifest["version"],
|
||||
manifest_hash,
|
||||
manifest.get("openUrl"),
|
||||
status="attention",
|
||||
)
|
||||
self.repository.add_log(
|
||||
task_id,
|
||||
"warning",
|
||||
"The package was installed but its service needs attention; it remains available for status and removal",
|
||||
)
|
||||
except Exception as tracking_error:
|
||||
self.repository.add_log(
|
||||
task_id,
|
||||
"warning",
|
||||
f"Could not persist installed package attention state: {tracking_error}",
|
||||
)
|
||||
|
||||
def _clean_cached_package_files(self, task_id: str, *identifiers: str | None) -> None:
|
||||
cache_dir = settings.cache_dir
|
||||
if not cache_dir.exists() or not cache_dir.is_dir():
|
||||
|
||||
Reference in New Issue
Block a user