use anyhow::Result;
use russh::client::Msg;
use russh::Channel;
use std::path::PathBuf;
use tokio::time::Duration;
use crate::config::{Config, InteractiveConfig};
use crate::node::Node;
use crate::pty::PtyConfig;
use crate::ssh::known_hosts::StrictHostKeyChecking;
use crate::ssh::tokio_client::Client;
pub const SSH_OUTPUT_POLL_INTERVAL_MS: u64 = 10;
pub const NODES_TO_SHOW_IN_COMPACT: usize = 3;
pub struct InteractiveCommand {
pub single_node: bool,
pub multiplex: bool,
pub prompt_format: String,
pub history_file: PathBuf,
pub work_dir: Option<String>,
pub nodes: Vec<Node>,
pub config: Config,
pub interactive_config: InteractiveConfig,
pub cluster_name: Option<String>,
pub key_path: Option<PathBuf>,
pub use_agent: bool,
pub use_password: bool,
#[cfg(target_os = "macos")]
pub use_keychain: bool,
pub strict_mode: StrictHostKeyChecking,
pub jump_hosts: Option<String>,
pub pty_config: PtyConfig,
pub use_pty: Option<bool>, }
#[derive(Debug)]
pub struct InteractiveResult {
pub duration: Duration,
pub commands_executed: usize,
pub nodes_connected: usize,
}
pub(super) struct NodeSession {
pub node: Node,
#[allow(dead_code)]
pub client: Client,
pub channel: Channel<Msg>,
pub working_dir: String,
pub is_connected: bool,
pub is_active: bool, }
impl NodeSession {
pub fn new(node: Node, client: Client, channel: Channel<Msg>, working_dir: String) -> Self {
Self {
node,
client,
channel,
working_dir,
is_connected: true,
is_active: true,
}
}
pub async fn send_command(&mut self, command: &str) -> Result<()> {
let data = format!("{command}\n");
self.channel.data(data.as_bytes()).await?;
Ok(())
}
pub async fn read_output(&mut self) -> Result<Option<String>> {
const SSH_OUTPUT_READ_TIMEOUT_MS: u64 = 100;
match tokio::time::timeout(
Duration::from_millis(SSH_OUTPUT_READ_TIMEOUT_MS),
self.channel.wait(),
)
.await
{
Ok(Some(msg)) => match msg {
russh::ChannelMsg::Data { ref data } => {
Ok(Some(String::from_utf8_lossy(data).to_string()))
}
russh::ChannelMsg::ExtendedData { ref data, ext } => {
if ext == 1 {
Ok(Some(String::from_utf8_lossy(data).to_string()))
} else {
Ok(None)
}
}
russh::ChannelMsg::Eof => {
self.is_connected = false;
Ok(None)
}
russh::ChannelMsg::Close => {
self.is_connected = false;
Ok(None)
}
_ => Ok(None),
},
Ok(None) => Ok(None),
Err(_) => Ok(None), }
}
}