feat: agent updates from SAC, poll config, SSH/WinRM fallback (0.12.0)
Add agent update settings, pending self-update via poll, desired config per host, and automatic or manual SSH/WinRM fallback when agents do not report agent.update events.
This commit is contained in:
@@ -11,6 +11,14 @@ from app.database import get_db
|
||||
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.agent_update import (
|
||||
AgentUpdateNotAllowedError,
|
||||
execute_agent_update_fallback,
|
||||
host_version_status,
|
||||
request_agent_update,
|
||||
)
|
||||
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
|
||||
from app.services.linux_admin_settings import get_effective_linux_admin_config
|
||||
from app.services.win_admin_settings import get_effective_win_admin_config
|
||||
@@ -82,6 +90,8 @@ def list_hosts(
|
||||
|
||||
hb_map = max_event_time_by_host(db, HEARTBEAT_TYPE)
|
||||
report_map = max_daily_report_time_by_host(db)
|
||||
latest_map = latest_agent_versions_by_product(db)
|
||||
update_cfg = get_effective_agent_update_config(db)
|
||||
|
||||
items: list[HostSummary] = []
|
||||
for host in rows:
|
||||
@@ -107,6 +117,8 @@ def list_hosts(
|
||||
agent_status=compute_agent_status(
|
||||
last_hb, stale_minutes=settings.sac_heartbeat_stale_minutes
|
||||
),
|
||||
version_status=host_version_status(host, cfg=update_cfg, latest_map=latest_map),
|
||||
pending_agent_update=bool(host.pending_agent_update),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -131,6 +143,8 @@ def _host_detail_from_model(
|
||||
select(func.count()).select_from(Event).where(Event.host_id == host.id)
|
||||
)
|
||||
last_hb = hb_map.get(host.id)
|
||||
latest_map = latest_agent_versions_by_product(db)
|
||||
update_cfg = get_effective_agent_update_config(db)
|
||||
return HostDetail(
|
||||
id=host.id,
|
||||
hostname=host.hostname,
|
||||
@@ -147,6 +161,8 @@ def _host_detail_from_model(
|
||||
last_daily_report_at=report_map.get(host.id),
|
||||
last_inventory_at=host.inventory_updated_at,
|
||||
agent_status=compute_agent_status(last_hb, stale_minutes=settings.sac_heartbeat_stale_minutes),
|
||||
version_status=host_version_status(host, cfg=update_cfg, latest_map=latest_map),
|
||||
pending_agent_update=bool(host.pending_agent_update),
|
||||
agent_instance_id=host.agent_instance_id,
|
||||
tags=host.tags if isinstance(host.tags, list) else [],
|
||||
use_sac_mode=host.use_sac_mode,
|
||||
@@ -155,6 +171,13 @@ def _host_detail_from_model(
|
||||
created_at=host.created_at,
|
||||
ssh_admin_ok=host.ssh_admin_ok,
|
||||
ssh_admin_checked_at=host.ssh_admin_checked_at,
|
||||
pending_update_requested_at=host.pending_update_requested_at,
|
||||
pending_update_target_version=host.pending_update_target_version,
|
||||
agent_config_revision=int(host.agent_config_revision or 0),
|
||||
agent_config=get_host_agent_settings(host) or None,
|
||||
agent_update_state=host.agent_update_state,
|
||||
agent_update_last_at=host.agent_update_last_at,
|
||||
agent_update_last_error=host.agent_update_last_error,
|
||||
)
|
||||
|
||||
|
||||
@@ -202,6 +225,22 @@ class HostSshActionResponse(BaseModel):
|
||||
ssh_admin_ok: bool | None = None
|
||||
|
||||
|
||||
class HostAgentUpdateRequestResponse(BaseModel):
|
||||
status: str
|
||||
pending_agent_update: bool
|
||||
target_version: str | None = None
|
||||
agent_update_state: str | None = None
|
||||
|
||||
|
||||
class HostAgentConfigUpdate(BaseModel):
|
||||
settings: dict[str, object] = {}
|
||||
|
||||
|
||||
class HostAgentConfigResponse(BaseModel):
|
||||
config_revision: int
|
||||
settings: dict[str, object]
|
||||
|
||||
|
||||
def _ssh_action_response(
|
||||
result: SshCommandResult,
|
||||
*,
|
||||
@@ -364,6 +403,87 @@ def update_host_agent_via_ssh(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{host_id}/actions/request-agent-update", response_model=HostAgentUpdateRequestResponse)
|
||||
def post_request_agent_update(
|
||||
host_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
_user=Depends(require_admin),
|
||||
) -> HostAgentUpdateRequestResponse:
|
||||
host = db.get(Host, host_id)
|
||||
if host is None:
|
||||
raise HTTPException(status_code=404, detail="Host not found")
|
||||
try:
|
||||
request_agent_update(db, host)
|
||||
except AgentUpdateNotAllowedError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
db.commit()
|
||||
return HostAgentUpdateRequestResponse(
|
||||
status="requested",
|
||||
pending_agent_update=host.pending_agent_update,
|
||||
target_version=host.pending_update_target_version,
|
||||
agent_update_state=host.agent_update_state,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{host_id}/actions/agent-update-fallback", response_model=HostSshActionResponse)
|
||||
def post_agent_update_fallback(
|
||||
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")
|
||||
|
||||
ok, message, version = execute_agent_update_fallback(db, host)
|
||||
db.commit()
|
||||
return HostSshActionResponse(
|
||||
ok=ok,
|
||||
message=message,
|
||||
target=host.hostname,
|
||||
product_version=version or (host.product_version if ok else None),
|
||||
ssh_admin_ok=host.ssh_admin_ok,
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/{host_id}/agent-config", response_model=HostAgentConfigResponse)
|
||||
def patch_host_agent_config(
|
||||
host_id: int,
|
||||
body: HostAgentConfigUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
_user=Depends(require_admin),
|
||||
) -> HostAgentConfigResponse:
|
||||
host = db.get(Host, host_id)
|
||||
if host is None:
|
||||
raise HTTPException(status_code=404, detail="Host not found")
|
||||
try:
|
||||
update_host_agent_config(db, host, body.settings)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
db.commit()
|
||||
settings = get_host_agent_settings(host)
|
||||
return HostAgentConfigResponse(
|
||||
config_revision=int(host.agent_config_revision or 0),
|
||||
settings=settings,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{host_id}/agent-config", response_model=HostAgentConfigResponse)
|
||||
def get_host_agent_config(
|
||||
host_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
_user=Depends(require_admin),
|
||||
) -> HostAgentConfigResponse:
|
||||
host = db.get(Host, host_id)
|
||||
if host is None:
|
||||
raise HTTPException(status_code=404, detail="Host not found")
|
||||
settings = get_host_agent_settings(host)
|
||||
return HostAgentConfigResponse(
|
||||
config_revision=int(host.agent_config_revision or 0),
|
||||
settings=settings,
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{host_id}", response_model=HostDeleteResponse)
|
||||
def delete_host(
|
||||
host_id: int,
|
||||
|
||||
Reference in New Issue
Block a user