update check active
This commit is contained in:
@@ -4,6 +4,9 @@ import uuid
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, HTTPException
|
||||
|
||||
from app.core.command_runner import CommandRunner
|
||||
from app.core.installer import AptInstaller
|
||||
from app.core.service_manager import ServiceManager
|
||||
from app.core.task_runner import TaskRunner
|
||||
from app.models.schemas import InstallRequest, RemoveRequest, UpdateRequest
|
||||
from app.storage.repository import Repository
|
||||
@@ -20,7 +23,56 @@ def _task_id(prefix: str) -> str:
|
||||
|
||||
@router.get("/installed")
|
||||
def installed_apps() -> list[dict]:
|
||||
return repository.list_installed_apps()
|
||||
apps = repository.list_installed_apps()
|
||||
command_runner = CommandRunner(repository)
|
||||
service_manager = ServiceManager(command_runner)
|
||||
apt_installer = AptInstaller(command_runner)
|
||||
for app in apps:
|
||||
service_checks: list[dict] = []
|
||||
component_statuses: list[str] = []
|
||||
for component in repository.list_installed_components(app["app_id"]):
|
||||
check_status = component.get("service_check_status", "not-checked")
|
||||
if check_status != "not-checked":
|
||||
component_statuses.append(check_status)
|
||||
for check in component.get("service_checks", []):
|
||||
service_name = str(check.get("serviceName") or "").strip()
|
||||
if not service_name:
|
||||
continue
|
||||
current_check = service_manager.get_service_status(service_name)
|
||||
if current_check.get("status") == "unknown" and current_check.get("errorMessage"):
|
||||
current_check = dict(check)
|
||||
current_check["stale"] = True
|
||||
elif check.get("readinessType") == "postgresql":
|
||||
readiness_status = "ready" if apt_installer.is_postgresql_ready() else "failed"
|
||||
current_check["readinessType"] = "postgresql"
|
||||
current_check["readinessStatus"] = readiness_status
|
||||
current_check["healthy"] = bool(current_check.get("healthy")) and readiness_status == "ready"
|
||||
service_checks.append(
|
||||
{
|
||||
**current_check,
|
||||
"componentId": component["component_id"],
|
||||
"packageName": component.get("package_name"),
|
||||
}
|
||||
)
|
||||
|
||||
if service_checks and any(not check.get("healthy") for check in service_checks):
|
||||
overall_status = "unhealthy"
|
||||
elif service_checks:
|
||||
overall_status = "healthy"
|
||||
elif "unhealthy" in component_statuses:
|
||||
overall_status = "unhealthy"
|
||||
elif "checking" in component_statuses:
|
||||
overall_status = "checking"
|
||||
elif "healthy" in component_statuses:
|
||||
overall_status = "healthy"
|
||||
elif "not-applicable" in component_statuses:
|
||||
overall_status = "not-applicable"
|
||||
else:
|
||||
overall_status = "not-checked"
|
||||
|
||||
app["serviceCheckStatus"] = overall_status
|
||||
app["serviceChecks"] = service_checks
|
||||
return apps
|
||||
|
||||
|
||||
@router.post("/install")
|
||||
|
||||
@@ -65,10 +65,11 @@ def get_task_components(task_id: str) -> dict:
|
||||
"progress": item["progress"],
|
||||
"currentStep": item["current_step"],
|
||||
"errorMessage": item["error_message"],
|
||||
"serviceCheckStatus": item["service_check_status"],
|
||||
"serviceChecks": item["service_checks"],
|
||||
"startedAt": item["started_at"],
|
||||
"finishedAt": item["finished_at"],
|
||||
}
|
||||
for item in repository.get_task_components(task_id)
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ def _bool(name: str, default: bool) -> bool:
|
||||
def get_settings() -> Settings:
|
||||
robot_package_base_url = os.getenv("ROBOT_PACKAGE_BASE_URL", "https://package.pnkr.cloud").rstrip("/")
|
||||
return Settings(
|
||||
agent_version=os.getenv("AGENT_VERSION", "1.0.0"),
|
||||
agent_version=os.getenv("AGENT_VERSION", "1.0.3"),
|
||||
host=os.getenv("AGENT_HOST", "0.0.0.0"),
|
||||
port=int(os.getenv("AGENT_PORT", "5010")),
|
||||
robot_package_base_url=robot_package_base_url,
|
||||
|
||||
@@ -51,6 +51,7 @@ class CommandRunner:
|
||||
command: list[str],
|
||||
timeout: int | None = None,
|
||||
env: Mapping[str, str] | None = None,
|
||||
log_output: bool = True,
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
if self.task_id:
|
||||
self.repository.add_log(self.task_id, "debug", f"Running command: {' '.join(command)}")
|
||||
@@ -74,7 +75,7 @@ class CommandRunner:
|
||||
self.repository.add_log(self.task_id, "error", f"Command timed out: {' '.join(command)}")
|
||||
raise CommandError(command, 124, error.stdout or "", error.stderr or "") from error
|
||||
|
||||
if self.task_id:
|
||||
if self.task_id and log_output:
|
||||
for line in result.stdout.splitlines():
|
||||
self.repository.add_log(self.task_id, "debug", line)
|
||||
for line in result.stderr.splitlines():
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from pathlib import Path
|
||||
from pathlib import Path, PurePosixPath
|
||||
|
||||
from app.core.command_runner import CommandError, CommandRunner
|
||||
|
||||
@@ -125,6 +125,38 @@ class AptInstaller(DebInstaller):
|
||||
env=APT_NONINTERACTIVE_ENV,
|
||||
)
|
||||
|
||||
def discover_service_units(self, package_name: str) -> list[str]:
|
||||
"""Return concrete systemd service units shipped by an installed package.
|
||||
|
||||
The package name is already constrained by the Agent APT allowlist. It is
|
||||
still passed as a single argv item and no shell expansion is performed.
|
||||
Template units are omitted because they cannot be meaningfully checked
|
||||
without an instance name.
|
||||
"""
|
||||
try:
|
||||
result = self.command_runner.run(
|
||||
["dpkg-query", "-L", package_name],
|
||||
timeout=30,
|
||||
log_output=False,
|
||||
)
|
||||
except CommandError:
|
||||
return []
|
||||
|
||||
service_names: set[str] = set()
|
||||
for raw_path in result.stdout.splitlines():
|
||||
path = raw_path.strip()
|
||||
if not path.endswith(".service"):
|
||||
continue
|
||||
if "/systemd/system/" not in path:
|
||||
continue
|
||||
|
||||
service_name = PurePosixPath(path).name
|
||||
if "@" in service_name:
|
||||
continue
|
||||
service_names.add(service_name)
|
||||
|
||||
return sorted(service_names)
|
||||
|
||||
def wait_for_postgresql(
|
||||
self,
|
||||
attempts: int = 6,
|
||||
@@ -141,3 +173,10 @@ class AptInstaller(DebInstaller):
|
||||
time.sleep(delay_seconds)
|
||||
|
||||
raise RuntimeError("PostgreSQL did not become ready after installation") from last_error
|
||||
|
||||
def is_postgresql_ready(self) -> bool:
|
||||
try:
|
||||
self.command_runner.run(["pg_isready", "--timeout=5"], timeout=10)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from app.core.command_runner import CommandError
|
||||
from app.core.command_runner import CommandRunner
|
||||
|
||||
|
||||
@@ -31,17 +34,60 @@ class ServiceManager:
|
||||
raise RuntimeError(f"Service is not active: {service_name}")
|
||||
|
||||
def get_service_status(self, service_name: str) -> dict[str, object]:
|
||||
active = self._query(["systemctl", "is-active", service_name]) == "active"
|
||||
enabled = self._query(["systemctl", "is-enabled", service_name]) == "enabled"
|
||||
command = [
|
||||
"systemctl",
|
||||
"show",
|
||||
service_name,
|
||||
"--no-pager",
|
||||
"--property=LoadState",
|
||||
"--property=ActiveState",
|
||||
"--property=SubState",
|
||||
"--property=UnitFileState",
|
||||
]
|
||||
error_message: str | None = None
|
||||
try:
|
||||
output = self.command_runner.run(command, timeout=5).stdout
|
||||
except CommandError as error:
|
||||
output = error.stdout
|
||||
error_message = str(error)
|
||||
except Exception as error:
|
||||
output = ""
|
||||
error_message = str(error)
|
||||
|
||||
properties = self._parse_properties(output)
|
||||
load_state = properties.get("LoadState", "unknown") or "unknown"
|
||||
active_state = properties.get("ActiveState", "unknown") or "unknown"
|
||||
sub_state = properties.get("SubState", "unknown") or "unknown"
|
||||
unit_file_state = properties.get("UnitFileState", "unknown") or "unknown"
|
||||
active = load_state == "loaded" and active_state == "active"
|
||||
enabled = unit_file_state in {"enabled", "enabled-runtime"}
|
||||
|
||||
if load_state == "not-found":
|
||||
status = "not-found"
|
||||
elif active_state != "unknown":
|
||||
status = active_state
|
||||
else:
|
||||
status = "unknown"
|
||||
|
||||
return {
|
||||
"serviceName": service_name,
|
||||
"loadState": load_state,
|
||||
"activeState": active_state,
|
||||
"subState": sub_state,
|
||||
"unitFileState": unit_file_state,
|
||||
"active": active,
|
||||
"enabled": enabled,
|
||||
"status": "running" if active else "stopped",
|
||||
"healthy": active,
|
||||
"status": status,
|
||||
"checkedAt": datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z"),
|
||||
"errorMessage": error_message,
|
||||
}
|
||||
|
||||
def _query(self, command: list[str]) -> str:
|
||||
try:
|
||||
return self.command_runner.run(command).stdout.strip()
|
||||
except Exception:
|
||||
return "unknown"
|
||||
@staticmethod
|
||||
def _parse_properties(output: str) -> dict[str, str]:
|
||||
properties: dict[str, str] = {}
|
||||
for line in output.splitlines():
|
||||
key, separator, value = line.partition("=")
|
||||
if separator and key:
|
||||
properties[key.strip()] = value.strip()
|
||||
return properties
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from app.config import settings
|
||||
@@ -55,6 +57,8 @@ CREATE TABLE IF NOT EXISTS installed_components (
|
||||
package_name TEXT,
|
||||
package_version TEXT,
|
||||
service_name TEXT,
|
||||
service_check_status TEXT DEFAULT 'not-checked',
|
||||
service_checks_json TEXT,
|
||||
docker_image TEXT,
|
||||
docker_digest TEXT,
|
||||
container_name TEXT,
|
||||
@@ -75,6 +79,8 @@ CREATE TABLE IF NOT EXISTS task_components (
|
||||
progress INTEGER DEFAULT 0,
|
||||
current_step TEXT,
|
||||
error_message TEXT,
|
||||
service_check_status TEXT DEFAULT 'not-checked',
|
||||
service_checks_json TEXT,
|
||||
started_at TEXT,
|
||||
finished_at TEXT,
|
||||
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE
|
||||
@@ -96,10 +102,18 @@ def _ensure_column(connection: sqlite3.Connection, table: str, column: str, defi
|
||||
connection.execute(f"ALTER TABLE {table} ADD COLUMN {column} {definition}")
|
||||
|
||||
|
||||
def get_connection() -> sqlite3.Connection:
|
||||
@contextmanager
|
||||
def get_connection() -> Iterator[sqlite3.Connection]:
|
||||
connection = sqlite3.connect(settings.db_path, timeout=30)
|
||||
connection.row_factory = sqlite3.Row
|
||||
return connection
|
||||
try:
|
||||
yield connection
|
||||
connection.commit()
|
||||
except Exception:
|
||||
connection.rollback()
|
||||
raise
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
def initialize_database() -> None:
|
||||
@@ -110,3 +124,17 @@ def initialize_database() -> None:
|
||||
with get_connection() as connection:
|
||||
connection.executescript(SCHEMA)
|
||||
_ensure_column(connection, "installed_apps", "open_url", "TEXT")
|
||||
_ensure_column(
|
||||
connection,
|
||||
"installed_components",
|
||||
"service_check_status",
|
||||
"TEXT DEFAULT 'not-checked'",
|
||||
)
|
||||
_ensure_column(connection, "installed_components", "service_checks_json", "TEXT")
|
||||
_ensure_column(
|
||||
connection,
|
||||
"task_components",
|
||||
"service_check_status",
|
||||
"TEXT DEFAULT 'not-checked'",
|
||||
)
|
||||
_ensure_column(connection, "task_components", "service_checks_json", "TEXT")
|
||||
|
||||
@@ -17,6 +17,29 @@ def row_to_dict(row: Any) -> dict[str, Any] | None:
|
||||
return dict(row)
|
||||
|
||||
|
||||
def _encode_service_checks(value: list[dict[str, Any]]) -> str:
|
||||
return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
|
||||
|
||||
|
||||
def _decode_service_checks(value: Any) -> list[dict[str, Any]]:
|
||||
if not value:
|
||||
return []
|
||||
try:
|
||||
decoded = json.loads(str(value))
|
||||
except (TypeError, ValueError, json.JSONDecodeError):
|
||||
return []
|
||||
if not isinstance(decoded, list):
|
||||
return []
|
||||
return [item for item in decoded if isinstance(item, dict)]
|
||||
|
||||
|
||||
def _component_row(row: Any) -> dict[str, Any]:
|
||||
item = dict(row)
|
||||
item["service_checks"] = _decode_service_checks(item.pop("service_checks_json", None))
|
||||
item["service_check_status"] = item.get("service_check_status") or "not-checked"
|
||||
return item
|
||||
|
||||
|
||||
class Repository:
|
||||
def create_task(self, task_id: str, task_type: str, app_id: str, app_name: str | None) -> None:
|
||||
now = utc_now()
|
||||
@@ -118,6 +141,8 @@ class Repository:
|
||||
progress: int | None = None,
|
||||
current_step: str | None = None,
|
||||
error_message: str | None = None,
|
||||
service_check_status: str | None = None,
|
||||
service_checks: list[dict[str, Any]] | None = None,
|
||||
started_at: str | None = None,
|
||||
finished_at: str | None = None,
|
||||
) -> None:
|
||||
@@ -128,6 +153,12 @@ class Repository:
|
||||
"progress": progress,
|
||||
"current_step": current_step,
|
||||
"error_message": error_message,
|
||||
"service_check_status": service_check_status,
|
||||
"service_checks_json": (
|
||||
_encode_service_checks(service_checks)
|
||||
if service_checks is not None
|
||||
else None
|
||||
),
|
||||
"started_at": started_at,
|
||||
"finished_at": finished_at,
|
||||
}.items():
|
||||
@@ -147,14 +178,15 @@ class Repository:
|
||||
with get_connection() as connection:
|
||||
rows = connection.execute(
|
||||
"""
|
||||
SELECT component_id, type, status, progress, current_step, error_message, started_at, finished_at
|
||||
SELECT component_id, type, status, progress, current_step, error_message,
|
||||
service_check_status, service_checks_json, started_at, finished_at
|
||||
FROM task_components
|
||||
WHERE task_id = ?
|
||||
ORDER BY install_order ASC, id ASC
|
||||
""",
|
||||
(task_id,),
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
return [_component_row(row) for row in rows]
|
||||
|
||||
def list_installed_apps(self) -> list[dict[str, Any]]:
|
||||
with get_connection() as connection:
|
||||
@@ -205,10 +237,11 @@ class Repository:
|
||||
"""
|
||||
INSERT INTO installed_components (
|
||||
app_id, component_id, type, install_order, status, package_name, package_version,
|
||||
service_name, docker_image, docker_digest, container_name, compose_project_name,
|
||||
service_name, service_check_status, service_checks_json,
|
||||
docker_image, docker_digest, container_name, compose_project_name,
|
||||
installed_at, updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, 'installed', ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, 'installed', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(app_id, component_id) DO UPDATE SET
|
||||
type = excluded.type,
|
||||
install_order = excluded.install_order,
|
||||
@@ -216,6 +249,8 @@ class Repository:
|
||||
package_name = excluded.package_name,
|
||||
package_version = excluded.package_version,
|
||||
service_name = excluded.service_name,
|
||||
service_check_status = excluded.service_check_status,
|
||||
service_checks_json = excluded.service_checks_json,
|
||||
docker_image = excluded.docker_image,
|
||||
docker_digest = excluded.docker_digest,
|
||||
container_name = excluded.container_name,
|
||||
@@ -230,6 +265,8 @@ class Repository:
|
||||
component.get("packageName"),
|
||||
component.get("version"),
|
||||
component.get("serviceName"),
|
||||
component.get("serviceCheckStatus", "not-checked"),
|
||||
_encode_service_checks(component.get("serviceChecks", [])),
|
||||
component.get("image"),
|
||||
component.get("digest"),
|
||||
component.get("containerName"),
|
||||
@@ -250,7 +287,7 @@ class Repository:
|
||||
""",
|
||||
(app_id,),
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
return [_component_row(row) for row in rows]
|
||||
|
||||
def export_manifest_hash(self, manifest: dict[str, Any]) -> str:
|
||||
return json.dumps(manifest, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
Reference in New Issue
Block a user