aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-10 23:06:01 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-10 23:06:01 +0300
commitf4d04fab26d8b8733868b8e394439b69ed83df75 (patch)
tree1657f7da96f4cc82a23b576d691887589c372013 /src
parent1fc5e84d461ff7c3cfcc0ed134df0939665fc937 (diff)
feat: add agent manifest update command
Diffstat (limited to 'src')
-rw-r--r--src/cli/server.rs143
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![
"--import-exe".to_string(),