ffs_cli/providers/
mod.rs

1use std::io::prelude::*;
2use std::net::TcpStream;
3use std::path::Path;
4
5use async_trait::async_trait;
6use ssh2::Session;
7
8use crate::jobs::Job;
9
10const INSTALL_SCRIPT: &str = include_str!(".././setup.sh");
11
12fn install_over_ssh(
13    ip: &str,
14    key_path: &str,
15) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
16    let tcp = TcpStream::connect((ip, 22))?;
17    let mut sess = Session::new()?;
18    sess.set_tcp_stream(tcp);
19    sess.handshake()?;
20    sess.userauth_pubkey_file("root", None, Path::new(key_path), None)?;
21
22    let mut channel = sess.channel_session()?;
23    channel.exec("bash -s")?;
24    channel.write_all(INSTALL_SCRIPT.as_bytes())?;
25    channel.send_eof()?;
26    channel.wait_close()?;
27
28    Ok(())
29}
30
31pub enum ProviderType {
32    Hetzner,
33    AWS,
34}
35
36#[async_trait]
37pub trait Provider: Send + Sync {
38    async fn start_job(&self, name: &str) -> Result<Job, Box<dyn std::error::Error + Send + Sync>>;
39    async fn get_job(
40        &self,
41        job_id: &str,
42    ) -> Result<Option<Job>, Box<dyn std::error::Error + Send + Sync>>;
43    async fn stop_job(&self, job_id: &str)
44        -> Result<Job, Box<dyn std::error::Error + Send + Sync>>;
45    async fn list_jobs(&self) -> Result<Vec<Job>, Box<dyn std::error::Error + Send + Sync>>;
46    async fn tail(
47        &self,
48        job_id: &str,
49        filename: &str,
50    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
51    async fn scp(
52        &self,
53        job_id: &str,
54        filename: &str,
55        destination: &str,
56    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
57
58    async fn install_dependencies(
59        &self,
60        ip: &str,
61        key_path: &str,
62    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
63        let ip = ip.to_string();
64        let key = key_path.to_string();
65        tokio::task::spawn_blocking(move || install_over_ssh(&ip, &key)).await??;
66        Ok(())
67    }
68}
69
70pub struct ProviderFactory;
71
72impl ProviderFactory {
73    /// Creates a new provider instance based on the provider name.
74    ///
75    /// # Panics
76    ///
77    /// Panics if an invalid provider name is provided.
78    #[must_use]
79    pub fn create_provider(name: &str) -> Box<dyn Provider> {
80        match name {
81            "hetzner" => Box::new(hetzner::HetznerProvider::new()),
82            "aws" => Box::new(aws::AWSProvider::new()),
83            _ => panic!("Invalid provider"),
84        }
85    }
86}
87
88pub mod aws;
89pub mod hetzner;