rilis 0.1.2

Local deployment tools built with Rust
Documentation
use anyhow::Result;
use async_trait::async_trait;
use log::info;
use russh::*;
use russh_keys::*;
use std::{path::Path, sync::Arc};
use tokio::net::ToSocketAddrs;

pub struct Client {}

#[async_trait]
impl client::Handler for Client {
    type Error = russh::Error;

    async fn check_server_key(
        &mut self,
        _server_public_key: &key::PublicKey,
    ) -> Result<bool, Self::Error> {
        Ok(true)
    }
}

pub struct Session {
    pub session: client::Handle<Client>,
}

impl Session {
    pub async fn connect<P: AsRef<Path>, A: ToSocketAddrs>(
        key_path: Option<P>,
        user: impl Into<String>,
        password: Option<String>,
        addrs: A,
    ) -> Result<Self> {
        let config = client::Config { ..<_>::default() };

        let config = Arc::new(config);
        let sh = Client {};

        let mut session = client::connect(config, addrs, sh).await?;
        let user = user.into();

        let auth_res = if let Some(key_path) = key_path {
            let key_pair = load_secret_key(key_path, None)?;
            session
                .authenticate_publickey(user, Arc::new(key_pair))
                .await?
        } else if let Some(password) = password {
            session.authenticate_password(user, password).await?
        } else {
            anyhow::bail!("Either key_path or password must be provided")
        };

        if !auth_res {
            anyhow::bail!("Authentication failed")
        };

        Ok(Self { session })
    }

    pub async fn call(&mut self, command: &str) -> Result<String> {
        info!("{command}");

        let mut channel = self.session.channel_open_session().await?;
        channel.exec(true, command).await?;

        let mut stdout_data = Vec::new();
        let mut stderr = String::new();

        loop {
            let Some(msg) = channel.wait().await else {
                break;
            };

            match msg {
                ChannelMsg::Data { ref data } => {
                    stdout_data.extend_from_slice(data);
                }
                ChannelMsg::ExtendedData { ref data, .. } => {
                    stderr.push_str(&String::from_utf8_lossy(data));
                }
                _ => {}
            }
        }

        if stdout_data.is_empty() {
            Ok(stderr)
        } else {
            Ok(String::from_utf8_lossy(&stdout_data).into_owned())
        }
    }

    pub async fn close(&mut self) -> Result<()> {
        self.session
            .disconnect(Disconnect::ByApplication, "", "English")
            .await?;

        Ok(())
    }
}