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 #[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;