cubic 0.17.0

Cubic is a lightweight command line manager for virtual machines. It has a simple, daemon-less and rootless design. All Cubic virtual machines run isolated in the user context. Cubic is built on top of QEMU, KVM and cloud-init. Show all supported images: $ cubic images Create a new virtual machine instance: $ cubic create mymachine --image ubuntu:noble List all virtual machine instances: $ cubic instances Start an instance: $ cubic start <instance name> Stop an instance: $ cubic stop <instance name> Open a shell in the instance: $ cubic ssh <machine name> Copy a file from the host to the instance: $ cubic scp <path/to/host/file> <machine>:<path/to/guest/file> Copy a file from the instance to the hots: $ cubic scp <machine>:<path/to/guest/file> <path/to/host/file>
use russh_sftp::{self, client::SftpSession};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::{self, fs};
use tokio::io::{AsyncRead, AsyncWrite};

#[derive(Clone)]
pub struct SftpPath {
    pub sftp: Option<Rc<SftpSession>>,
    pub path: PathBuf,
}

impl SftpPath {
    pub fn name(&self) -> String {
        self.path.file_name().unwrap().to_str().unwrap().to_string()
    }

    pub fn to_str(&self) -> &str {
        self.path.to_str().unwrap()
    }

    pub async fn exists(&self) -> bool {
        match &self.sftp {
            None => self.path.exists(),
            Some(sftp) => sftp.try_exists(self.to_str()).await.unwrap(),
        }
    }

    pub async fn is_file(&self) -> bool {
        match &self.sftp {
            None => self.path.is_file(),
            Some(sftp) => sftp
                .metadata(self.to_str())
                .await
                .unwrap()
                .file_type()
                .is_file(),
        }
    }

    pub async fn is_dir(&self) -> bool {
        match &self.sftp {
            None => self.path.is_dir(),
            Some(sftp) => sftp
                .metadata(self.to_str())
                .await
                .unwrap()
                .file_type()
                .is_dir(),
        }
    }

    pub fn append(&self, name: &str) -> Self {
        let mut path = self.path.clone();
        path.push(name);
        Self {
            sftp: self.sftp.clone(),
            path,
        }
    }

    pub async fn open_file(&self) -> Box<dyn AsyncRead + Unpin> {
        match &self.sftp {
            None => Box::new(tokio::fs::File::open(self.path.clone()).await.unwrap()),
            Some(sftp) => Box::new(sftp.open(self.to_str()).await.unwrap()),
        }
    }

    pub async fn create_file(&self) -> Box<dyn AsyncWrite + Unpin> {
        match &self.sftp {
            None => Box::new(tokio::fs::File::create(self.path.clone()).await.unwrap()),
            Some(sftp) => Box::new(sftp.create(self.to_str()).await.unwrap()),
        }
    }

    pub async fn write_file(&self, mut content: Box<dyn AsyncRead + Unpin>) {
        tokio::io::copy(content.as_mut(), &mut self.create_file().await)
            .await
            .unwrap();
    }

    pub async fn read_dir(&self) -> Vec<SftpPath> {
        let mut children = Vec::new();
        match &self.sftp {
            None => {
                if let Ok(dir) = self.path.read_dir() {
                    for entry in dir.flatten() {
                        children.push(Self {
                            sftp: None,
                            path: entry.path(),
                        });
                    }
                }
            }
            Some(sftp) => {
                for entry in sftp.read_dir(self.to_str()).await.unwrap() {
                    children.push(Self {
                        sftp: Some(sftp.clone()),
                        path: Path::new(&format!("{}/{}", self.to_str(), entry.file_name()))
                            .to_path_buf(),
                    });
                }
            }
        }
        children
    }

    pub async fn create_path(&self) {
        match &self.sftp {
            None => fs::create_dir(self.path.clone()).unwrap(),
            Some(sftp) => sftp.create_dir(self.to_str()).await.unwrap(),
        }
    }

    pub async fn recursive_copy(&self, target: SftpPath) {
        if self.is_file().await {
            let reader = self.open_file().await;
            if target.exists().await && target.is_dir().await {
                target.append(&self.name()).write_file(reader).await;
            } else {
                target.write_file(reader).await;
            }
        } else if self.is_dir().await {
            let target_dir = target.append(&self.name());
            target_dir.create_path().await;
            for entry in self.read_dir().await {
                Box::pin(entry.recursive_copy(target_dir.clone())).await;
            }
        }
    }

    pub async fn copy(&self, target: SftpPath) {
        if target.exists().await || self.is_file().await {
            self.recursive_copy(target).await;
        } else if self.is_dir().await {
            target.create_path().await;
            for entry in self.read_dir().await {
                entry.recursive_copy(target.clone()).await;
            }
        }
    }
}