feat: background remote agent updates survive SAC navigation (0.20.19)
This commit is contained in:
+99
-35
@@ -1,10 +1,9 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from functools import partial
|
||||
|
||||
from app.auth.jwt_auth import get_current_user, require_admin
|
||||
from app.config import get_settings
|
||||
@@ -18,10 +17,16 @@ from app.services.agent_version import (
|
||||
from app.services.agent_git_release import get_git_release_versions
|
||||
from app.services.agent_update import (
|
||||
AgentUpdateNotAllowedError,
|
||||
execute_agent_update_fallback,
|
||||
host_version_status,
|
||||
request_agent_update,
|
||||
)
|
||||
from app.services.host_remote_actions import (
|
||||
RemoteActionAlreadyRunningError,
|
||||
get_remote_action_status,
|
||||
run_agent_fallback_action,
|
||||
run_ssh_monitor_update_action,
|
||||
start_host_remote_action,
|
||||
)
|
||||
from app.services.agent_update_settings import get_effective_agent_update_config
|
||||
from app.services.agent_host_config import get_host_agent_settings, update_host_agent_config
|
||||
from app.services.host_delete import delete_host_and_related
|
||||
@@ -264,6 +269,32 @@ class HostAgentConfigResponse(BaseModel):
|
||||
settings: dict[str, object]
|
||||
|
||||
|
||||
class HostRemoteActionStartResponse(BaseModel):
|
||||
status: str
|
||||
host_id: int
|
||||
title: str
|
||||
message: str
|
||||
|
||||
|
||||
class HostRemoteActionJobResponse(BaseModel):
|
||||
active: bool
|
||||
host_id: int
|
||||
hostname: str | None = None
|
||||
status: str | None = None
|
||||
title: str = ""
|
||||
message: str = ""
|
||||
stdout: str | None = None
|
||||
stderr: str | None = None
|
||||
output: str | None = None
|
||||
ok: bool | None = None
|
||||
exit_code: int | None = None
|
||||
product_version: str | None = None
|
||||
target: str | None = None
|
||||
agent_update_state: str | None = None
|
||||
started_at: str | None = None
|
||||
finished_at: str | None = None
|
||||
|
||||
|
||||
def _ssh_action_response(
|
||||
result: SshCommandResult,
|
||||
*,
|
||||
@@ -399,12 +430,16 @@ def test_host_ssh(
|
||||
return response.model_copy(update={"ssh_admin_ok": host.ssh_admin_ok})
|
||||
|
||||
|
||||
@router.post("/{host_id}/actions/agent-update", response_model=HostSshActionResponse)
|
||||
@router.post(
|
||||
"/{host_id}/actions/agent-update",
|
||||
response_model=HostRemoteActionStartResponse,
|
||||
status_code=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
def update_host_agent_via_ssh(
|
||||
host_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
_user=Depends(require_admin),
|
||||
) -> HostSshActionResponse:
|
||||
) -> HostRemoteActionStartResponse:
|
||||
host = db.get(Host, host_id)
|
||||
if host is None:
|
||||
raise HTTPException(status_code=404, detail="Host not found")
|
||||
@@ -413,23 +448,21 @@ def update_host_agent_via_ssh(
|
||||
if not cfg.configured:
|
||||
raise HTTPException(status_code=400, detail="Linux SSH admin is not configured")
|
||||
|
||||
agent_cfg = get_effective_agent_update_config(db)
|
||||
repo_url = (agent_cfg.ssh_git_repo_url or "").strip() or None
|
||||
branch = (agent_cfg.git_branch or "main").strip() or "main"
|
||||
response = _run_ssh_action_on_host(
|
||||
host,
|
||||
cfg,
|
||||
action=partial(run_ssh_monitor_update, repo_url=repo_url, git_branch=branch),
|
||||
)
|
||||
_set_ssh_admin_status(host, response.ok)
|
||||
if response.ok and response.product_version:
|
||||
host.product_version = response.product_version
|
||||
db.commit()
|
||||
return response.model_copy(
|
||||
update={
|
||||
"product_version": host.product_version if response.ok else response.product_version,
|
||||
"ssh_admin_ok": host.ssh_admin_ok,
|
||||
}
|
||||
title = "Обновление ssh-monitor (SSH)"
|
||||
try:
|
||||
start_host_remote_action(
|
||||
db,
|
||||
host,
|
||||
title=title,
|
||||
runner=run_ssh_monitor_update_action,
|
||||
)
|
||||
except RemoteActionAlreadyRunningError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
||||
return HostRemoteActionStartResponse(
|
||||
status="running",
|
||||
host_id=host.id,
|
||||
title=title,
|
||||
message="Обновление запущено на сервере SAC и продолжится в фоне",
|
||||
)
|
||||
|
||||
|
||||
@@ -455,30 +488,61 @@ def post_request_agent_update(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{host_id}/actions/agent-update-fallback", response_model=HostSshActionResponse)
|
||||
@router.post(
|
||||
"/{host_id}/actions/agent-update-fallback",
|
||||
response_model=HostRemoteActionStartResponse,
|
||||
status_code=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
def post_agent_update_fallback(
|
||||
host_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
_user=Depends(require_admin),
|
||||
) -> HostSshActionResponse:
|
||||
) -> HostRemoteActionStartResponse:
|
||||
host = db.get(Host, host_id)
|
||||
if host is None:
|
||||
raise HTTPException(status_code=404, detail="Host not found")
|
||||
|
||||
result = execute_agent_update_fallback(db, host)
|
||||
db.commit()
|
||||
return HostSshActionResponse(
|
||||
ok=result.ok,
|
||||
message=result.message,
|
||||
target=result.target or host.hostname,
|
||||
stdout=result.stdout or None,
|
||||
stderr=result.stderr or None,
|
||||
exit_code=result.exit_code,
|
||||
product_version=result.product_version or (host.product_version if result.ok else None),
|
||||
ssh_admin_ok=host.ssh_admin_ok,
|
||||
from app.services.agent_update_settings import PRODUCT_RDP, PRODUCT_SSH
|
||||
from app.services.ssh_connect import is_linux_host
|
||||
from app.services.winrm_connect import is_windows_host
|
||||
|
||||
product = (host.product or "").strip()
|
||||
if product == PRODUCT_RDP or is_windows_host(host):
|
||||
title = "Обновление через WinRM"
|
||||
elif product == PRODUCT_SSH or is_linux_host(host):
|
||||
title = "Fallback SSH (ssh-monitor)"
|
||||
else:
|
||||
title = "Обновление агента (fallback)"
|
||||
|
||||
try:
|
||||
start_host_remote_action(
|
||||
db,
|
||||
host,
|
||||
title=title,
|
||||
runner=run_agent_fallback_action,
|
||||
)
|
||||
except RemoteActionAlreadyRunningError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
||||
return HostRemoteActionStartResponse(
|
||||
status="running",
|
||||
host_id=host.id,
|
||||
title=title,
|
||||
message="Обновление запущено на сервере SAC и продолжится в фоне",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{host_id}/actions/remote-job", response_model=HostRemoteActionJobResponse)
|
||||
def get_host_remote_job(
|
||||
host_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
_user=Depends(require_admin),
|
||||
) -> HostRemoteActionJobResponse:
|
||||
host = db.get(Host, host_id)
|
||||
if host is None:
|
||||
raise HTTPException(status_code=404, detail="Host not found")
|
||||
return HostRemoteActionJobResponse(**get_remote_action_status(host))
|
||||
|
||||
|
||||
@router.patch("/{host_id}/agent-config", response_model=HostAgentConfigResponse)
|
||||
def patch_host_agent_config(
|
||||
host_id: int,
|
||||
|
||||
Reference in New Issue
Block a user