use dora_message::{cli_to_coordinator::ControlRequest, coordinator_to_cli::DaemonInfo};
use eyre::Context;
use std::collections::BTreeMap;
use crate::{
command::Executable,
common::{expect_reply, send_control_request},
ws_client::WsSession,
};
use self::config::MachineConfig;
pub mod config;
mod down;
mod install;
mod restart;
mod status;
mod uninstall;
mod up;
mod upgrade;
#[derive(Debug, clap::Subcommand)]
pub enum Cluster {
Up(up::Up),
Status(status::Status),
Down(down::Down),
Install(install::Install),
Uninstall(uninstall::Uninstall),
Upgrade(upgrade::Upgrade),
Restart(restart::Restart),
}
impl Executable for Cluster {
fn execute(self) -> eyre::Result<()> {
match self {
Cluster::Up(cmd) => cmd.execute(),
Cluster::Status(cmd) => cmd.execute(),
Cluster::Down(cmd) => cmd.execute(),
Cluster::Install(cmd) => cmd.execute(),
Cluster::Uninstall(cmd) => cmd.execute(),
Cluster::Upgrade(cmd) => cmd.execute(),
Cluster::Restart(cmd) => cmd.execute(),
}
}
}
pub(crate) fn query_connected_daemons(session: &WsSession) -> eyre::Result<Vec<DaemonInfo>> {
let reply = send_control_request(session, &ControlRequest::ConnectedMachines)?;
Ok(expect_reply!(reply, ConnectedDaemons(daemons))?)
}
pub(super) fn ssh_target(machine: &MachineConfig) -> String {
match &machine.user {
Some(user) => format!("{user}@{}", machine.host),
None => machine.host.clone(),
}
}
pub(super) fn format_labels_arg(labels: &BTreeMap<String, String>) -> String {
if labels.is_empty() {
String::new()
} else {
let pairs: Vec<String> = labels.iter().map(|(k, v)| format!("{k}={v}")).collect();
format!(" --labels {}", pairs.join(","))
}
}
pub(super) fn format_daemon_port_arg(daemon_port: Option<u16>) -> String {
match daemon_port {
Some(p) => format!(" --local-listen-port {p}"),
None => String::new(),
}
}
pub(super) fn format_zenoh_peer_arg(zenoh_peer: Option<&str>) -> String {
match zenoh_peer {
Some(ep) => format!(" --zenoh-peer {ep}"),
None => String::new(),
}
}
pub(super) fn run_ssh(target: &str, port: Option<u16>, cmd: &str) -> eyre::Result<bool> {
let mut command = std::process::Command::new("ssh");
command.args([
"-o",
"BatchMode=yes",
"-o",
"ConnectTimeout=10",
"-o",
"StrictHostKeyChecking=accept-new",
]);
if let Some(p) = port {
command.args(["-p", &p.to_string()]);
}
command.args([target, cmd]);
let status = command
.status()
.with_context(|| format!("failed to run ssh to {target}"))?;
Ok(status.success())
}
pub(super) fn record_ssh_result(
failures: &mut Vec<(String, String)>,
machine_id: &str,
result: eyre::Result<bool>,
ok_msg: &str,
) {
match result {
Ok(true) => println!(" OK: {ok_msg}"),
Ok(false) => {
let msg = "ssh command failed".to_string();
eprintln!(" FAILED: {msg}");
failures.push((machine_id.to_owned(), msg));
}
Err(err) => {
let msg = format!("{err}");
eprintln!(" FAILED: {msg}");
failures.push((machine_id.to_owned(), msg));
}
}
}
pub(super) fn print_summary(action: &str, total: usize, failures: &[(String, String)]) {
if failures.is_empty() {
println!("All {total} {action}");
} else {
println!("{}/{total} {action}", total - failures.len());
for (id, reason) in failures {
eprintln!(" {id}: {reason}");
}
}
}