feat: Linux SSH admin and remote ssh-monitor update (0.11.0)

This commit is contained in:
PTah
2026-06-20 00:25:35 +10:00
parent 279ea925a8
commit cc8f50de89
17 changed files with 805 additions and 5 deletions
+98
View File
@@ -10,6 +10,7 @@ from app.models import Event, Host
from app.schemas.list_models import HostDetail, HostListResponse, HostSummary
from app.services.agent_version import latest_agent_versions_by_product
from app.services.host_delete import delete_host_and_related
from app.services.linux_admin_settings import get_effective_linux_admin_config
from app.services.win_admin_settings import get_effective_win_admin_config
from app.services.winrm_connect import (
HostNotWindowsError,
@@ -19,6 +20,15 @@ from app.services.winrm_connect import (
iter_winrm_targets,
test_winrm_connection,
)
from app.services.ssh_connect import (
HostNotLinuxError as SshHostNotLinuxError,
HostTargetMissingError as SshHostTargetMissingError,
LinuxAdminNotConfiguredError,
SshCommandResult,
iter_ssh_targets,
run_ssh_monitor_update,
test_ssh_connection,
)
from app.services.host_health import (
HEARTBEAT_TYPE,
agent_status as compute_agent_status,
@@ -172,6 +182,60 @@ class HostWinRmTestResponse(BaseModel):
hostname: str | None = None
class HostSshActionResponse(BaseModel):
ok: bool
message: str
target: str
stdout: str | None = None
stderr: str | None = None
exit_code: int | None = None
def _ssh_action_response(result: SshCommandResult, *, attempts: list[str] | None = None) -> HostSshActionResponse:
message = result.message
if attempts:
message = f"{message} (пробовали: {', '.join(attempts)})"
return HostSshActionResponse(
ok=result.ok,
message=message,
target=result.target,
stdout=result.stdout or None,
stderr=result.stderr or None,
exit_code=result.exit_code,
)
def _run_ssh_action_on_host(
host: Host,
cfg,
*,
action,
) -> HostSshActionResponse:
try:
targets = iter_ssh_targets(host)
except SshHostNotLinuxError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
except SshHostTargetMissingError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
last_result: SshCommandResult | None = None
attempts: list[str] = []
for target in targets:
try:
result = action(target=target, user=cfg.user, password=cfg.password)
except LinuxAdminNotConfiguredError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
except RuntimeError as exc:
raise HTTPException(status_code=503, detail=str(exc)) from exc
last_result = result
attempts.append(f"{target}={'OK' if result.ok else 'fail'}")
if result.ok:
return _ssh_action_response(result, attempts=attempts if len(attempts) > 1 else None)
assert last_result is not None
return _ssh_action_response(last_result, attempts=attempts if len(attempts) > 1 else None)
@router.post("/{host_id}/actions/winrm-test", response_model=HostWinRmTestResponse)
def test_host_winrm(
host_id: int,
@@ -235,6 +299,40 @@ def test_host_winrm(
)
@router.post("/{host_id}/actions/ssh-test", response_model=HostSshActionResponse)
def test_host_ssh(
host_id: int,
db: Session = Depends(get_db),
_user=Depends(require_admin),
) -> HostSshActionResponse:
host = db.get(Host, host_id)
if host is None:
raise HTTPException(status_code=404, detail="Host not found")
cfg = get_effective_linux_admin_config(db)
if not cfg.configured:
raise HTTPException(status_code=400, detail="Linux SSH admin is not configured")
return _run_ssh_action_on_host(host, cfg, action=test_ssh_connection)
@router.post("/{host_id}/actions/agent-update", response_model=HostSshActionResponse)
def update_host_agent_via_ssh(
host_id: int,
db: Session = Depends(get_db),
_user=Depends(require_admin),
) -> HostSshActionResponse:
host = db.get(Host, host_id)
if host is None:
raise HTTPException(status_code=404, detail="Host not found")
cfg = get_effective_linux_admin_config(db)
if not cfg.configured:
raise HTTPException(status_code=400, detail="Linux SSH admin is not configured")
return _run_ssh_action_on_host(host, cfg, action=run_ssh_monitor_update)
@router.delete("/{host_id}", response_model=HostDeleteResponse)
def delete_host(
host_id: int,