rusible 0.0.0

[WIP] Writing infrastructure automation tasks in Rust.
Documentation
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> {
        // 跳过主机密钥验证,直接返回 true
        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 {
            // There's an event available on the session channel
            let Some(msg) = channel.wait().await else {
                break;
            };
            match msg {
                // Write data to the terminal
                ChannelMsg::Data { ref data } => {
                    stdout.write_all(data).await?;
                    stdout.flush().await?;
                }
                // The command has returned an exit code
                ChannelMsg::ExitStatus { exit_status } => {
                    code = Some(exit_status);
                    // cannot leave the loop immediately, there might still be more data to receive
                }
                _ => {}
            }
        }

        Ok(code.expect("program did not exit cleanly"))
    }
}