use std::sync::Arc;
use russh::{client::{self, Handle}, keys::ssh_key, ChannelMsg};
use tokio::io::AsyncWriteExt;
pub struct Host {
pub name: String,
pub host: String,
pub port: u16,
pub user: String,
pub password: Option<String>,
}
pub struct Client;
impl client::Handler for Client {
type Error = russh::Error;
async fn check_server_key(
&mut self,
_server_public_key: &ssh_key::PublicKey,
) -> Result<bool, Self::Error> {
Ok(true)
}
}
pub struct Inventory {
sessions: Vec<(Host, Handle<Client>)>,
}
impl Inventory {
pub async fn new(hosts: impl IntoIterator<Item = Host>) -> crate::Result<Self> {
let mut sessions = Vec::new();
for host in hosts.into_iter() {
let config = Arc::new(russh::client::Config {
inactivity_timeout: Some(std::time::Duration::from_secs(5)),
..Default::default()
});
let mut session = client::connect(config, (&*host.host, host.port), Client).await?;
session.authenticate_password(&host.user, &host.password.as_ref().cloned().unwrap()).await?;
sessions.push((host, session));
}
Ok(Self {
sessions,
})
}
pub async fn test(&mut self) -> crate::Result<u32> {
let mut channel = self.sessions[0].1.channel_open_session().await?;
channel.exec(true, "whoami").await?;
let mut code = None;
let mut stdout = tokio::io::stdout();
loop {
let Some(msg) = channel.wait().await else {
break;
};
match msg {
ChannelMsg::Data { ref data } => {
stdout.write_all(data).await?;
stdout.flush().await?;
}
ChannelMsg::ExitStatus { exit_status } => {
code = Some(exit_status);
}
_ => {}
}
}
Ok(code.expect("program did not exit cleanly"))
}
}