diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/cli/server.rs | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/src/cli/server.rs b/src/cli/server.rs index f6cf7e54..b0648cde 100644 --- a/src/cli/server.rs +++ b/src/cli/server.rs @@ -11,6 +11,7 @@ pub(super) fn run_server_command(args: &[String]) -> std::io::Result<Option<i32> "--handoff-import" => Ok(None), "reload-config" => server_reload_config(&args[1..]).map(Some), "agent-manifests" => server_agent_manifests(&args[1..]).map(Some), + "update-agent-manifests" => server_update_agent_manifests(&args[1..]).map(Some), "reload-agent-manifests" => server_reload_agent_manifests(&args[1..]).map(Some), "help" | "--help" | "-h" => { print_server_help(); @@ -78,6 +79,69 @@ fn server_reload_agent_manifests(args: &[String]) -> std::io::Result<i32> { })?) } +fn server_update_agent_manifests(args: &[String]) -> std::io::Result<i32> { + let json = match args { + [] => false, + [flag] if flag == "--json" => true, + _ => { + eprintln!("usage: herdr server update-agent-manifests [--json]"); + return Ok(2); + } + }; + + let response = match update_agent_manifest_status(super::send_request, || { + crate::detect::manifest_update::check_and_update().map(|_| ()) + })? { + Ok(response) => response, + Err(err) => { + if json { + return super::print_response(&agent_manifest_update_error_response(&err)); + } + eprintln!("failed to update agent detection manifests: {err}"); + return Ok(1); + } + }; + if json || response.get("error").is_some() { + return super::print_response(&response); + } + + print_agent_manifest_status(&response); + Ok(0) +} + +fn update_agent_manifest_status( + mut send_request: impl FnMut(&Request) -> std::io::Result<serde_json::Value>, + update_manifests: impl FnOnce() -> Result<(), String>, +) -> std::io::Result<Result<serde_json::Value, String>> { + if let Err(err) = update_manifests() { + return Ok(Err(err)); + } + + let reload_response = send_request(&Request { + id: "cli:server:reload-agent-manifests".into(), + method: Method::ServerReloadAgentManifests(EmptyParams::default()), + })?; + if reload_response.get("error").is_some() { + return Ok(Ok(reload_response)); + } + + send_request(&Request { + id: "cli:server:agent-manifests".into(), + method: Method::ServerAgentManifests(EmptyParams::default()), + }) + .map(Ok) +} + +fn agent_manifest_update_error_response(err: &str) -> serde_json::Value { + serde_json::json!({ + "id": "cli:server:update-agent-manifests", + "error": { + "code": "agent_manifest_update_failed", + "message": err, + } + }) +} + fn print_agent_manifest_status(response: &serde_json::Value) { let result = &response["result"]; let last_check = result["last_check_unix"] @@ -187,6 +251,7 @@ fn print_server_help() { eprintln!(" herdr server live-handoff hand off live panes to a new local server"); eprintln!(" herdr server reload-config reload config.toml in the running server"); eprintln!(" herdr server agent-manifests [--json] show agent detection manifest status"); + eprintln!(" herdr server update-agent-manifests [--json] fetch and reload agent detection manifests"); eprintln!(" herdr server reload-agent-manifests reload agent detection manifests in the running server"); } @@ -195,6 +260,84 @@ mod tests { use super::*; #[test] + fn update_agent_manifest_status_fetches_reloads_then_reads_status() { + let mut methods = Vec::new(); + let response = update_agent_manifest_status( + |request| { + methods.push(request.method.clone()); + match &request.method { + Method::ServerReloadAgentManifests(_) => Ok(serde_json::json!({ + "id": request.id, + "result": { "type": "agent_manifest_reload", "manifests": [] } + })), + Method::ServerAgentManifests(_) => Ok(serde_json::json!({ + "id": request.id, + "result": { + "type": "agent_manifest_status", + "last_result": "checked", + "manifests": [] + } + })), + _ => panic!("unexpected request"), + } + }, + || Ok(()), + ) + .unwrap() + .unwrap(); + + assert_eq!(response["result"]["type"], "agent_manifest_status"); + assert_eq!( + methods, + vec![ + Method::ServerReloadAgentManifests(EmptyParams::default()), + Method::ServerAgentManifests(EmptyParams::default()) + ] + ); + } + + #[test] + fn update_agent_manifest_status_skips_server_when_fetch_fails() { + let response = update_agent_manifest_status( + |_request| panic!("server should not be called after fetch failure"), + || Err("network unavailable".to_string()), + ) + .unwrap(); + + assert_eq!(response, Err("network unavailable".to_string())); + assert_eq!( + agent_manifest_update_error_response("network unavailable")["error"]["code"], + "agent_manifest_update_failed" + ); + } + + #[test] + fn update_agent_manifest_status_stops_after_reload_error() { + let mut methods = Vec::new(); + let response = update_agent_manifest_status( + |request| { + methods.push(request.method.clone()); + Ok(serde_json::json!({ + "id": request.id, + "error": { + "code": "reload_failed", + "message": "reload failed" + } + })) + }, + || Ok(()), + ) + .unwrap() + .unwrap(); + + assert_eq!(response["error"]["code"], "reload_failed"); + assert_eq!( + methods, + vec![Method::ServerReloadAgentManifests(EmptyParams::default())] + ); + } + + #[test] fn live_handoff_params_parse_remote_update_fields() { let args = vec