split hivectl main.rs into per-domain modules (#2509)
This commit is contained in:
parent
673aea4e50
commit
fc7720572b
16 changed files with 1922 additions and 1771 deletions
164
hivectl/src/agents.rs
Normal file
164
hivectl/src/agents.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
//! `hivectl agents` — container lifecycle over the host admin socket
|
||||
//! (list/restart/restart-all/spawn/kill/destroy/rebuild/set-parent).
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use hive_host_sock::HostRequest;
|
||||
|
||||
use crate::cli::AgentsCmd;
|
||||
use crate::dag_progress::wait_for_dags;
|
||||
use crate::util::render;
|
||||
|
||||
async fn agents_restart(socket: &Path, name: &str, no_wait: bool) -> Result<()> {
|
||||
let resp = crate::client::request(
|
||||
socket,
|
||||
hive_host_sock::HostRequest::Restart {
|
||||
name: name.to_owned(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("connect to daemon socket {}", socket.display()))?;
|
||||
if resp.ok {
|
||||
println!("restart queued: {name}");
|
||||
wait_for_dags(socket, resp.queued_dags.unwrap_or_default(), no_wait).await
|
||||
} else {
|
||||
bail!(
|
||||
"restart {name}: {}",
|
||||
resp.error.as_deref().unwrap_or("unknown error")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// `hivectl agents list` — fetch the per-agent status roster from the
|
||||
/// daemon (`HostRequest::AgentStatus`) and render it as a padded table,
|
||||
/// or the raw JSON rows with `--json`. Reuses the dashboard's
|
||||
/// `ContainerView` aggregation, so the CLI and the web UI never drift.
|
||||
async fn agents_list(socket: &Path, json: bool) -> Result<()> {
|
||||
let resp = crate::client::request(socket, hive_host_sock::HostRequest::AgentStatus)
|
||||
.await
|
||||
.with_context(|| format!("connect to daemon socket {}", socket.display()))?;
|
||||
if !resp.ok {
|
||||
bail!(
|
||||
"agents list: {}",
|
||||
resp.error.as_deref().unwrap_or("unknown error")
|
||||
);
|
||||
}
|
||||
let rows = resp.agent_statuses.unwrap_or_default();
|
||||
if json {
|
||||
println!("{}", serde_json::to_string_pretty(&rows)?);
|
||||
return Ok(());
|
||||
}
|
||||
if rows.is_empty() {
|
||||
println!("no managed agents found");
|
||||
return Ok(());
|
||||
}
|
||||
// STATUS collapses the health flags into one space-separated token so
|
||||
// the common case (`running`) stays short and anomalies stand out.
|
||||
let status_of = |r: &hive_sh4re::AgentStatusRow| -> String {
|
||||
let mut s = if r.running { "running" } else { "stopped" }.to_owned();
|
||||
if r.needs_login {
|
||||
s.push_str(" needs-login");
|
||||
}
|
||||
if r.needs_update {
|
||||
s.push_str(" needs-update");
|
||||
}
|
||||
s
|
||||
};
|
||||
let headers = ["NAME", "STATUS", "REV", "PARENT", "REMIND"];
|
||||
let table: Vec<[String; 5]> = rows
|
||||
.iter()
|
||||
.map(|r| {
|
||||
[
|
||||
r.name.clone(),
|
||||
status_of(r),
|
||||
r.deployed_sha.clone().unwrap_or_else(|| "-".to_owned()),
|
||||
r.parent.clone().unwrap_or_else(|| "-".to_owned()),
|
||||
if r.pending_reminders > 0 {
|
||||
r.pending_reminders.to_string()
|
||||
} else {
|
||||
"-".to_owned()
|
||||
},
|
||||
]
|
||||
})
|
||||
.collect();
|
||||
let mut widths = headers.map(str::len);
|
||||
for row in &table {
|
||||
for (i, cell) in row.iter().enumerate() {
|
||||
widths[i] = widths[i].max(cell.len());
|
||||
}
|
||||
}
|
||||
let fmt_row = |cells: &[String]| -> String {
|
||||
cells
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, c)| format!("{c:<w$}", w = widths[i]))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
.trim_end()
|
||||
.to_owned()
|
||||
};
|
||||
let header_cells: Vec<String> = headers.iter().map(|h| (*h).to_owned()).collect();
|
||||
println!("{}", fmt_row(&header_cells));
|
||||
for row in &table {
|
||||
println!("{}", fmt_row(row));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn agents_restart_all(socket: &Path, no_wait: bool) -> Result<()> {
|
||||
let resp = crate::client::request(socket, hive_host_sock::HostRequest::RestartAll)
|
||||
.await
|
||||
.with_context(|| format!("connect to daemon socket {}", socket.display()))?;
|
||||
let agents = resp.agents.as_deref().unwrap_or(&[]);
|
||||
if agents.is_empty() {
|
||||
println!("restart-all: no managed containers found");
|
||||
} else {
|
||||
for a in agents {
|
||||
println!("restart queued: {a}");
|
||||
}
|
||||
}
|
||||
if !resp.ok {
|
||||
bail!(
|
||||
"restart-all: {}",
|
||||
resp.error.as_deref().unwrap_or("unknown error")
|
||||
);
|
||||
}
|
||||
wait_for_dags(socket, resp.queued_dags.unwrap_or_default(), no_wait).await
|
||||
}
|
||||
|
||||
/// Dispatch `hivectl agents <verb>` — container lifecycle over the host
|
||||
/// admin socket.
|
||||
pub(crate) async fn run_agents(socket: &Path, cmd: AgentsCmd) -> Result<()> {
|
||||
match cmd {
|
||||
AgentsCmd::List { json } => agents_list(socket, json).await,
|
||||
AgentsCmd::Restart { name, no_wait } => agents_restart(socket, &name, no_wait).await,
|
||||
AgentsCmd::RestartAll { no_wait } => agents_restart_all(socket, no_wait).await,
|
||||
AgentsCmd::Spawn { name } => {
|
||||
render(crate::client::request(socket, HostRequest::Spawn { name }).await?)
|
||||
}
|
||||
AgentsCmd::RequestSpawn { name } => {
|
||||
render(crate::client::request(socket, HostRequest::RequestSpawn { name }).await?)
|
||||
}
|
||||
AgentsCmd::Kill { name } => {
|
||||
render(crate::client::request(socket, HostRequest::Kill { name }).await?)
|
||||
}
|
||||
AgentsCmd::Destroy { name, purge } => {
|
||||
render(crate::client::request(socket, HostRequest::Destroy { name, purge }).await?)
|
||||
}
|
||||
AgentsCmd::Rebuild { name } => {
|
||||
render(crate::client::request(socket, HostRequest::Rebuild { name }).await?)
|
||||
}
|
||||
AgentsCmd::SetParent {
|
||||
child,
|
||||
parent,
|
||||
root,
|
||||
} => {
|
||||
let new_parent = if root { None } else { parent };
|
||||
render(
|
||||
crate::client::request(socket, HostRequest::SetParent { child, new_parent })
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue